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