Initial commit
This commit is contained in:
commit
b5c3a49513
|
@ -0,0 +1,5 @@
|
|||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
# DoorBell-MQTT-Gateway
|
||||
|
||||
Based on
|
||||
|
||||
- Arduino Uno
|
||||
- Ethernet interface
|
||||
|
||||
Watchout for GPIO changes as triggers for
|
||||
|
||||
- door bell input
|
||||
- post box observer (post entry)
|
||||
- post box observer (takeout posts)
|
||||
- ...
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the usual convention is to give header files names that end with `.h'.
|
||||
It is most portable to use only letters, digits, dashes, and underscores in
|
||||
header file names, and at most one dot.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into executable file.
|
||||
|
||||
The source code of each library should be placed in a an own separate directory
|
||||
("lib/your_library_name/[here are source files]").
|
||||
|
||||
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
and a contents of `src/main.c`:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
|
@ -0,0 +1,18 @@
|
|||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:uno]
|
||||
platform = atmelavr
|
||||
board = uno
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson@^6.17.2
|
||||
knolleary/PubSubClient@^2.8
|
||||
paulstoffregen/Ethernet@0.0.0-alpha+sha.9f41e8231b
|
|
@ -0,0 +1,318 @@
|
|||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include <Ethernet.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <string.h>
|
||||
|
||||
// Update these with values suitable for your network.
|
||||
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xEA };
|
||||
IPAddress ip(192, 168, 89, 205);
|
||||
IPAddress mqttServerIP(192, 168, 89, 95);
|
||||
const char *mqttUser = "default";
|
||||
const char *mqttPassword = "12345678";
|
||||
|
||||
const char *doorBellTopic = "doorBell";
|
||||
const char *postboxFlapTopic = "postboxFlap";
|
||||
const char *postboxDoorTopic = "postboxDoor";
|
||||
const char *doorBellTriggerSoundTopic = "doorBellTrigger";
|
||||
const char *postboxLightTopic = "postboxLight";
|
||||
|
||||
EthernetClient ethClient;
|
||||
EthernetServer server(80);
|
||||
PubSubClient mqttClient(ethClient);
|
||||
#define DOOR_BELL_BUTTON_PIN 7
|
||||
#define POSTBOX_FLAP_PIN 5
|
||||
#define POSTBOX_DOOR_PIN 3
|
||||
// Output pins
|
||||
#define LED_PIN 13
|
||||
#define DOOR_BELL_BUZZER_PIN 6
|
||||
#define POSTBOX_LIGHT_PIN 4
|
||||
|
||||
struct {
|
||||
const uint8_t pin;
|
||||
const unsigned long quietAfterTriggerFor_ms;
|
||||
unsigned long lastTrigger_ts;
|
||||
bool isTriggered;
|
||||
const char *topic;
|
||||
} myInputs[] = {
|
||||
{ DOOR_BELL_BUTTON_PIN, 500, 0 , false, doorBellTopic },
|
||||
{ POSTBOX_FLAP_PIN, 1000, 0, false, postboxFlapTopic },
|
||||
{ POSTBOX_DOOR_PIN, 1000, 0, false, postboxDoorTopic }
|
||||
};
|
||||
#define NUM_INPUTS (sizeof(myInputs) / sizeof(myInputs[0]))
|
||||
int ledState = LOW;
|
||||
|
||||
struct {
|
||||
const uint8_t pin;
|
||||
const unsigned long triggerTime_ms;
|
||||
bool isTriggered;
|
||||
unsigned long lastTrigger_ts;
|
||||
const char *listenTopic; // can be triggered by mqtt message as well
|
||||
} myOutputs[] = {
|
||||
{ DOOR_BELL_BUZZER_PIN, 500, false, 0, doorBellTriggerSoundTopic},
|
||||
{ POSTBOX_LIGHT_PIN, 30000, false, 0, postboxLightTopic}
|
||||
};
|
||||
#define NUM_OUTPUTS (sizeof(myOutputs) / sizeof(myOutputs[0]))
|
||||
|
||||
|
||||
void switchOutputOn(unsigned int outputNumber) {
|
||||
Serial.print(F("Switch output on: "));
|
||||
Serial.print(outputNumber);
|
||||
Serial.print(F(" (pin "));
|
||||
Serial.print(myOutputs[outputNumber].pin);
|
||||
Serial.print(F(") topic "));
|
||||
Serial.println(myOutputs[outputNumber].listenTopic);
|
||||
digitalWrite(myOutputs[outputNumber].pin, LOW);
|
||||
myOutputs[outputNumber].isTriggered = true;
|
||||
myOutputs[outputNumber].lastTrigger_ts = millis();
|
||||
}
|
||||
|
||||
void switchOutputOff(unsigned int outputNumber) {
|
||||
Serial.print(F("Switch output off: "));
|
||||
Serial.print(outputNumber);
|
||||
Serial.print(F(" (pin "));
|
||||
Serial.print(myOutputs[outputNumber].pin);
|
||||
Serial.print(F(") topic "));
|
||||
Serial.println(myOutputs[outputNumber].listenTopic);
|
||||
digitalWrite(myOutputs[outputNumber].pin, HIGH);
|
||||
myOutputs[outputNumber].isTriggered = false;
|
||||
}
|
||||
|
||||
void checkOutputAutoOff() {
|
||||
static bool headerDone = false;
|
||||
for (unsigned int outputNumber=0; outputNumber<NUM_OUTPUTS; ++outputNumber) {
|
||||
|
||||
if (myOutputs[outputNumber].isTriggered) {
|
||||
if (!headerDone) {
|
||||
Serial.print(F("checkOutputAutoOff: ts=")); Serial.println(millis());
|
||||
headerDone = true;
|
||||
}
|
||||
//Serial.print(outputNumber); Serial.print(": ");
|
||||
//Serial.println(myOutputs[outputNumber].lastTrigger_ts + myOutputs[outputNumber].triggerTime_ms);
|
||||
if ((myOutputs[outputNumber].lastTrigger_ts + myOutputs[outputNumber].triggerTime_ms) < millis()) {
|
||||
Serial.println(F("Auto off output:"));
|
||||
switchOutputOff(outputNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mqttReceiveMessage(char* topic, uint8_t* payload, unsigned int length) {
|
||||
Serial.print(F("Message arrived ["));
|
||||
Serial.print(topic);
|
||||
Serial.print(F("] "));
|
||||
Serial.print(length);
|
||||
Serial.print(F(" bytes: "));
|
||||
Serial.println((char *) payload);
|
||||
for (unsigned int i=0;i<length;i++) {
|
||||
Serial.print((char)payload[i]);
|
||||
}
|
||||
Serial.println();
|
||||
bool handlerFound = false;
|
||||
for (unsigned int i=0; i<NUM_OUTPUTS; ++i) {
|
||||
if (strcmp(myOutputs[i].listenTopic, topic) == 0) {
|
||||
handlerFound = true;
|
||||
if (strncmp((char *) payload, "on", length < 2 ? length : 2) == 0) {
|
||||
switchOutputOn(i);
|
||||
} else if (strncmp((char *) payload, "off", length < 3 ? length : 3) == 0) {
|
||||
switchOutputOff(i);
|
||||
} else {
|
||||
Serial.print(F("Unknown MQTT message payload received, topic: "));
|
||||
Serial.print(topic);
|
||||
Serial.print(F(", payload: "));
|
||||
Serial.println((char *) payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!handlerFound) {
|
||||
Serial.print(F("Unknown MQTT message received, topic: "));
|
||||
Serial.print(topic);
|
||||
Serial.print(F(", payload: "));
|
||||
Serial.println((char *) payload);
|
||||
}
|
||||
}
|
||||
|
||||
void reconnect() {
|
||||
// Loop until we're reconnected
|
||||
while (!mqttClient.connected()) {
|
||||
Serial.print(F("Attempting MQTT connection..."));
|
||||
// Attempt to connect
|
||||
if (mqttClient.connect("doorBellGateway", mqttUser, mqttPassword)) {
|
||||
Serial.println(F("connected"));
|
||||
// Once connected, publish an announcement...
|
||||
// mqttClient.publish("hello","hello world");
|
||||
// ... and resubscribe
|
||||
Serial.println(F("MQTT subscribe doorbell"));
|
||||
if (!mqttClient.subscribe("doorbell")) {
|
||||
Serial.println(F("ERROR: Failed to subscribe"));
|
||||
}
|
||||
for (unsigned int i=0; i<NUM_OUTPUTS; ++i) {
|
||||
Serial.print(F("MQTT subscribe ")); Serial.println(myOutputs[i].listenTopic);
|
||||
if (!mqttClient.subscribe(myOutputs[i].listenTopic)) {
|
||||
Serial.println(F("ERROR: Failed to subscribe"));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Serial.print(F("failed, rc="));
|
||||
Serial.print(mqttClient.state());
|
||||
Serial.println(F(" try again in 5 seconds"));
|
||||
// Wait 5 seconds before retrying
|
||||
delay(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendMqttMessage(const char *topic, const char *message) {
|
||||
reconnect();
|
||||
mqttClient.publish(topic, message);
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
// Initialize serial port
|
||||
Serial.begin(9600);
|
||||
while (!Serial) continue;
|
||||
Serial.println(F("Arduino Door Bell MQTT Gateway started."));
|
||||
|
||||
mqttClient.setServer(mqttServerIP, 1883);
|
||||
mqttClient.setCallback(mqttReceiveMessage);
|
||||
|
||||
// Initialize Ethernet libary
|
||||
if (!Ethernet.begin(mac)) {
|
||||
Serial.println(F("Failed to initialize Ethernet library"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Start to listen
|
||||
server.begin();
|
||||
Serial.println(F("Server is ready."));
|
||||
Serial.print(F("Please connect to http://"));
|
||||
Serial.println(Ethernet.localIP());
|
||||
|
||||
// setup the input and output pins
|
||||
for (unsigned int i = 0; i < NUM_INPUTS; i++) {
|
||||
pinMode(myInputs[i].pin, INPUT_PULLUP);
|
||||
}
|
||||
|
||||
// Setup the LED :
|
||||
for (unsigned int i=0; i < NUM_OUTPUTS; ++i) {
|
||||
pinMode(myOutputs[i].pin, OUTPUT);
|
||||
switchOutputOff(i);
|
||||
}
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, ledState);
|
||||
}
|
||||
|
||||
|
||||
void checkInputSignals() {
|
||||
bool needToToggleLed = false;
|
||||
|
||||
|
||||
for (unsigned int i = 0; i < NUM_INPUTS; i++) {
|
||||
if (millis()-myInputs[i].lastTrigger_ts > myInputs[i].quietAfterTriggerFor_ms) {
|
||||
if (!digitalRead(myInputs[i].pin)) {
|
||||
// input is triggered, compare with existing state
|
||||
if (myInputs[i].isTriggered) {
|
||||
// was triggered as well, could send a retrigger now
|
||||
Serial.print(F("Re-Trigger pin ")); Serial.println(myInputs[i].pin);
|
||||
sendMqttMessage(myInputs[i].topic, "re-trigger");
|
||||
needToToggleLed = true;
|
||||
} else {
|
||||
// handle trigger now
|
||||
myInputs[i].isTriggered = true;
|
||||
// send Trigger
|
||||
Serial.print(F("Trigger pin ")); Serial.println(myInputs[i].pin);
|
||||
sendMqttMessage(myInputs[i].topic, "trigger");
|
||||
needToToggleLed = true;
|
||||
}
|
||||
myInputs[i].lastTrigger_ts = millis();
|
||||
} else {
|
||||
// no trigger seen, if it was before, send a "released" message
|
||||
if (myInputs[i].isTriggered) {
|
||||
myInputs[i].isTriggered = false;
|
||||
myInputs[i].lastTrigger_ts = millis();
|
||||
Serial.print(F("Released pin ")); Serial.println(myInputs[i].pin);
|
||||
sendMqttMessage(myInputs[i].topic, "release");
|
||||
needToToggleLed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if a LED toggle has been flagged :
|
||||
if ( needToToggleLed ) {
|
||||
// Toggle LED state :
|
||||
ledState = !ledState;
|
||||
digitalWrite(LED_PIN, ledState);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void checkWebRequest() {
|
||||
// Wait for an incomming connection
|
||||
EthernetClient client = server.available();
|
||||
|
||||
// Do we have a client?
|
||||
if (!client) return;
|
||||
|
||||
Serial.println(F("New client on web ingress"));
|
||||
|
||||
// Read the request (we ignore the content in this example)
|
||||
while (client.available()) client.read();
|
||||
|
||||
// Allocate a temporary JsonDocument
|
||||
// Use arduinojson.org/v6/assistant to compute the capacity.
|
||||
StaticJsonDocument<500> doc;
|
||||
|
||||
for (unsigned int pin = 0; pin < NUM_INPUTS; pin++) {
|
||||
doc[myInputs[pin].topic] = myInputs[pin].isTriggered ? "triggered" : "released";
|
||||
}
|
||||
|
||||
// Create the "digital" array
|
||||
JsonArray digitalValues = doc.createNestedArray("digitalPin");
|
||||
for (unsigned int pin = 0; pin < 14; pin++) {
|
||||
// Read the digital input
|
||||
int value = digitalRead(pin);
|
||||
|
||||
// Add the value at the end of the array
|
||||
digitalValues.add(value);
|
||||
}
|
||||
|
||||
Serial.print(F("Sending: "));
|
||||
serializeJson(doc, Serial);
|
||||
Serial.println();
|
||||
|
||||
// Write response headers
|
||||
client.println(F("HTTP/1.0 200 OK"));
|
||||
client.println(F("Content-Type: application/json"));
|
||||
client.println(F("Connection: close"));
|
||||
client.print(F("Content-Length: "));
|
||||
// client.println(measureJsonPretty(doc));
|
||||
client.println(measureJson(doc));
|
||||
client.println();
|
||||
|
||||
// Write JSON document
|
||||
//serializeJsonPretty(doc, client);
|
||||
serializeJson(doc, client);
|
||||
|
||||
// Disconnect
|
||||
client.stop();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Client
|
||||
if (!mqttClient.connected()) {
|
||||
reconnect();
|
||||
}
|
||||
mqttClient.loop();
|
||||
|
||||
checkInputSignals();
|
||||
|
||||
checkWebRequest();
|
||||
|
||||
checkOutputAutoOff();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
This directory is intended for PlatformIO Unit Testing and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/page/plus/unit-testing.html
|
Loading…
Reference in New Issue