From 5b30e0317d5e71b8e967d096e4b0d10d430ed395 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Wed, 24 Apr 2024 11:18:13 +0200 Subject: [PATCH 01/61] fix warning about const char & upgrade arduino json library --- gateway/include/Protocol_1.h | 2 +- gateway/platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gateway/include/Protocol_1.h b/gateway/include/Protocol_1.h index f05e7f8..935904e 100644 --- a/gateway/include/Protocol_1.h +++ b/gateway/include/Protocol_1.h @@ -11,7 +11,7 @@ public: void fromJson(JsonObjectConst& rcSwitch, RCSwitch& rcDevice) override { unsigned int protocol = rcSwitch["protocol"]; rcDevice.setProtocol(protocol); - char* group = rcSwitch["group"]; + const char* group = rcSwitch["group"]; int channel = rcSwitch["channel"]; rcSwitch["state"] ? rcDevice.switchOn(group, channel) : rcDevice.switchOff(group, channel); } diff --git a/gateway/platformio.ini b/gateway/platformio.ini index 2e57c72..12f2a27 100644 --- a/gateway/platformio.ini +++ b/gateway/platformio.ini @@ -19,7 +19,7 @@ lib_extra_dirs = ../libraries lib_deps = sui77/rc-switch@^2.6.3 - bblanchon/ArduinoJson@6.19.4 + bblanchon/ArduinoJson@6.21.5 adafruit/Adafruit Unified Sensor@^1.1.4 adafruit/DHT sensor library@1.3.10 https://git.hodos.ro/arduino/lib_serial-reader.git@^1.0.0 From 100a89a92f6049e7365bf8fc06f86959bd877fa9 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Fri, 26 Apr 2024 13:25:10 +0200 Subject: [PATCH 02/61] add huzzah with support for ota and wifi --- gateway/.gitignore | 1 + gateway/include/credentials.h.tpl | 4 ++ gateway/include/ota.h | 32 +++++++++++++ gateway/include/wifi.h | 74 +++++++++++++++++++++++++++++++ gateway/platformio.ini | 19 ++++++++ gateway/src/gateway.cpp | 24 ++++++++++ 6 files changed, 154 insertions(+) create mode 100644 gateway/include/credentials.h.tpl create mode 100644 gateway/include/ota.h create mode 100644 gateway/include/wifi.h diff --git a/gateway/.gitignore b/gateway/.gitignore index 89cc49c..3074d78 100644 --- a/gateway/.gitignore +++ b/gateway/.gitignore @@ -3,3 +3,4 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +include/credentials.h diff --git a/gateway/include/credentials.h.tpl b/gateway/include/credentials.h.tpl new file mode 100644 index 0000000..c5637e4 --- /dev/null +++ b/gateway/include/credentials.h.tpl @@ -0,0 +1,4 @@ +struct WifiCredentials { + const char* ssid; + const char* password; +} credentials[] = {"foo", "bar"}; diff --git a/gateway/include/ota.h b/gateway/include/ota.h new file mode 100644 index 0000000..b834a8a --- /dev/null +++ b/gateway/include/ota.h @@ -0,0 +1,32 @@ +#include + +namespace Ota { + + void loop(); + Task tLoop(TASK_IMMEDIATE, TASK_FOREVER, loop, &ts, true); + + void setup() { + ArduinoOTA.onStart([]() { + Serial.println("Start"); + }); + ArduinoOTA.onEnd([]() { + Serial.println("\nEnd"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + ArduinoOTA.begin(); + } + + void loop() { + ArduinoOTA.handle(); + } +} diff --git a/gateway/include/wifi.h b/gateway/include/wifi.h new file mode 100644 index 0000000..c00d88d --- /dev/null +++ b/gateway/include/wifi.h @@ -0,0 +1,74 @@ +#include +#include +#include +#include "credentials.h" + +namespace Wifi { + + WiFiEventHandler stationConnectedHandler; + WiFiEventHandler stationDisconnectedHandler; + void reconnect(); + + Task tWifiReconnect(1 * TASK_MINUTE, TASK_FOREVER, reconnect, &ts); + + String currentSSID; + String currentPsk; + + void setup() { + stationConnectedHandler = WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP& e) { + Serial.println("Reconnected to network."); + tWifiConnected.restart(); + tWifiReconnect.cancel(); + }); + + stationDisconnectedHandler = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected& e) { + Serial.println("Disconnected from network."); + tWifiReconnect.restartDelayed(); + }); + + + ESP8266WiFiMulti wifiMulti; + for (uint32_t i = 0; i < sizeof(credentials) / sizeof(WifiCredentials); i++) { + wifiMulti.addAP(credentials[i].ssid, credentials[i].password); + } + + Serial.println("Connecting to WiFi netowrk."); + while (wifiMulti.run() != WL_CONNECTED) { + delay(500); + } + WiFi.setHostname("esp-clock"); + currentSSID = WiFi.SSID(); + currentPsk = WiFi.psk(); + } + + void reconnect() { + if (WiFi.status() != WL_CONNECTED) { + WiFi.forceSleepWake(); + WiFi.begin(currentSSID.c_str(), currentPsk.c_str()); + Serial.println("Reconnecting to WiFi netowrk..."); + } + } + + void disconnect() { + Serial.println("Disconnecting WiFi"); + WiFi.disconnect(); + WiFi.forceSleepBegin(); + } + + void printStatus() { + // print the SSID of the network you're attached to: + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + + // print your WiFi shield's IP address: + IPAddress ip = WiFi.localIP(); + Serial.print("IP Address: "); + Serial.println(ip); + + // print the received signal strength: + long rssi = WiFi.RSSI(); + Serial.print("signal strength (RSSI):"); + Serial.print(rssi); + Serial.println(" dBm"); + } +} diff --git a/gateway/platformio.ini b/gateway/platformio.ini index 12f2a27..4828ef1 100644 --- a/gateway/platformio.ini +++ b/gateway/platformio.ini @@ -11,6 +11,25 @@ [platformio] default_envs = pro-mini +[env:huzzah] +platform = espressif8266 +board = huzzah +framework = arduino +lib_extra_dirs = + ../libraries +lib_deps = + sui77/rc-switch@^2.6.4 + bblanchon/ArduinoJson@6.21.5 + adafruit/Adafruit Unified Sensor@^1.1.4 + arkhipenko/TaskScheduler@^3.7.0 + https://git.hodos.ro/arduino/lib_serial-reader.git@^1.0.0 +build_flags = -D DHT_SENSOR=0 -D DEBUG_RAW=0 +upload_port = /dev/ttyUSB0 +check_tool = cppcheck +check_flags = --enable=all +check_skip_packages = yes +check_severity = medium, high + [env:pro-mini] platform = atmelavr board = pro16MHzatmega328 diff --git a/gateway/src/gateway.cpp b/gateway/src/gateway.cpp index 4a8e019..defd07a 100644 --- a/gateway/src/gateway.cpp +++ b/gateway/src/gateway.cpp @@ -10,6 +10,15 @@ #define SEND_PIN 11 #define RECEIVE_PIN 2 +#if defined(ESP8266) +void onWifiConnected(); +#include +Scheduler ts; +Task tWifiConnected(TASK_IMMEDIATE, TASK_ONCE, onWifiConnected, &ts); + +#include "wifi.h" +#include "ota.h" +#endif RCSwitch mySwitch; SerialReader<200> serialReader; @@ -24,6 +33,10 @@ void setup() { mySwitch.setRepeatTransmit(10); Dht::setup(); +#if defined(ESP8266) + Wifi::setup(); + Ota::setup(); +#endif Serial.begin(9600); @@ -113,4 +126,15 @@ void loop() { readCommand(); readRcSwitch(); Dht::read(); +#if defined(ESP8266) + ts.execute(); +#endif } + +#if defined(ESP8266) +void onWifiConnected() { + Serial.println("Wifi connected event"); + Wifi::printStatus(); + Ota::tLoop.enable(); +} +#endif \ No newline at end of file From 61a7ca0bde4beae582c2c7e9d2064895d7dff767 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Sat, 27 Apr 2024 00:00:11 +0200 Subject: [PATCH 03/61] separate pro-mini and huzzah logic into dedicated files --- gateway/include/huzzah.h | 29 ++++++++++++++++++ gateway/include/ota.h | 9 +++--- gateway/include/pro-mini.h | 37 +++++++++++++++++++++++ gateway/include/wifi.h | 41 ++++++++++++++------------ gateway/platformio.ini | 8 +++-- gateway/src/gateway.cpp | 60 +++++++------------------------------- 6 files changed, 110 insertions(+), 74 deletions(-) create mode 100644 gateway/include/huzzah.h create mode 100644 gateway/include/pro-mini.h diff --git a/gateway/include/huzzah.h b/gateway/include/huzzah.h new file mode 100644 index 0000000..431fcba --- /dev/null +++ b/gateway/include/huzzah.h @@ -0,0 +1,29 @@ +#include + +#define SEND_PIN PIN_SPI_MOSI +#define RECEIVE_PIN PIN_SPI_MISO +#define RED_LED LED_BUILTIN +// #define BLUE_LED 2 + +Scheduler ts; + +#include "wifi.h" + +namespace Board { + void turnOffLed(uint8_t led) { + digitalWrite(led, HIGH); + } + + void setup() { + pinMode(RED_LED, OUTPUT); + turnOffLed(RED_LED); + // pinMode(BLUE_LED, OUTPUT); + // turnOffLed(BLUE_LED); + Wifi::setup(); + Ota::setup(); + } + + void loop() { + ts.execute(); + } +} diff --git a/gateway/include/ota.h b/gateway/include/ota.h index b834a8a..6015b5c 100644 --- a/gateway/include/ota.h +++ b/gateway/include/ota.h @@ -2,7 +2,10 @@ namespace Ota { - void loop(); + void loop() { + ArduinoOTA.handle(); + } + Task tLoop(TASK_IMMEDIATE, TASK_FOREVER, loop, &ts, true); void setup() { @@ -25,8 +28,4 @@ namespace Ota { }); ArduinoOTA.begin(); } - - void loop() { - ArduinoOTA.handle(); - } } diff --git a/gateway/include/pro-mini.h b/gateway/include/pro-mini.h new file mode 100644 index 0000000..6da5bb9 --- /dev/null +++ b/gateway/include/pro-mini.h @@ -0,0 +1,37 @@ +#include + +#define RESET_PIN 10 +#define SEND_PIN 11 +#define RECEIVE_PIN 2 + +namespace Board { + SerialReader<200> serialReader; + + void setup() { + digitalWrite(RESET_PIN, HIGH); + pinMode(RESET_PIN, OUTPUT); + } + + void blink() { + digitalWrite(LED_BUILTIN, HIGH); + delay(200); + digitalWrite(LED_BUILTIN, LOW); + } + + void readCommand() { + if (serialReader.readLine(Serial) > 0) { + char* cmd = serialReader.getBuffer(); + if (strcmp("reset", cmd) == 0) { + Serial.println("resetting..."); + delay(1200); + digitalWrite(RESET_PIN, LOW); + Serial.println("resetting failed"); + } + runJsonCommand(cmd); + } + } + + void loop() { + readCommand(); + } +} diff --git a/gateway/include/wifi.h b/gateway/include/wifi.h index c00d88d..0b9219e 100644 --- a/gateway/include/wifi.h +++ b/gateway/include/wifi.h @@ -1,33 +1,46 @@ #include #include #include +#include "ota.h" #include "credentials.h" namespace Wifi { WiFiEventHandler stationConnectedHandler; WiFiEventHandler stationDisconnectedHandler; - void reconnect(); - - Task tWifiReconnect(1 * TASK_MINUTE, TASK_FOREVER, reconnect, &ts); + ESP8266WiFiMulti wifiMulti; String currentSSID; String currentPsk; + void printStatus(); + + Task tReconnect(1 * TASK_MINUTE, TASK_FOREVER, [](){ + if (WiFi.status() != WL_CONNECTED) { + WiFi.forceSleepWake(); + WiFi.begin(currentSSID.c_str(), currentPsk.c_str()); + Serial.println("Reconnecting to WiFi netowrk..."); + } + }, &ts); + Task tConnected(TASK_IMMEDIATE, TASK_ONCE, [](){ + Serial.println("Wifi connected event"); + printStatus(); + Ota::tLoop.enable(); + }, &ts); + void setup() { stationConnectedHandler = WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP& e) { Serial.println("Reconnected to network."); - tWifiConnected.restart(); - tWifiReconnect.cancel(); - }); + tConnected.restart(); + tReconnect.cancel(); + }); stationDisconnectedHandler = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected& e) { Serial.println("Disconnected from network."); - tWifiReconnect.restartDelayed(); - }); + tReconnect.restartDelayed(); + }); - ESP8266WiFiMulti wifiMulti; for (uint32_t i = 0; i < sizeof(credentials) / sizeof(WifiCredentials); i++) { wifiMulti.addAP(credentials[i].ssid, credentials[i].password); } @@ -36,19 +49,11 @@ namespace Wifi { while (wifiMulti.run() != WL_CONNECTED) { delay(500); } - WiFi.setHostname("esp-clock"); + WiFi.setHostname("rc-gateway"); currentSSID = WiFi.SSID(); currentPsk = WiFi.psk(); } - void reconnect() { - if (WiFi.status() != WL_CONNECTED) { - WiFi.forceSleepWake(); - WiFi.begin(currentSSID.c_str(), currentPsk.c_str()); - Serial.println("Reconnecting to WiFi netowrk..."); - } - } - void disconnect() { Serial.println("Disconnecting WiFi"); WiFi.disconnect(); diff --git a/gateway/platformio.ini b/gateway/platformio.ini index 4828ef1..3faf2e2 100644 --- a/gateway/platformio.ini +++ b/gateway/platformio.ini @@ -22,13 +22,17 @@ lib_deps = bblanchon/ArduinoJson@6.21.5 adafruit/Adafruit Unified Sensor@^1.1.4 arkhipenko/TaskScheduler@^3.7.0 + adafruit/DHT sensor library@1.3.2 https://git.hodos.ro/arduino/lib_serial-reader.git@^1.0.0 build_flags = -D DHT_SENSOR=0 -D DEBUG_RAW=0 -upload_port = /dev/ttyUSB0 check_tool = cppcheck check_flags = --enable=all check_skip_packages = yes check_severity = medium, high +upload_port = 192.168.6.161 +upload_protocol = espota +upload_flags = + --host_port=10000 [env:pro-mini] platform = atmelavr @@ -40,7 +44,7 @@ lib_deps = sui77/rc-switch@^2.6.3 bblanchon/ArduinoJson@6.21.5 adafruit/Adafruit Unified Sensor@^1.1.4 - adafruit/DHT sensor library@1.3.10 + adafruit/DHT sensor library@1.3.2 https://git.hodos.ro/arduino/lib_serial-reader.git@^1.0.0 build_flags = -D DHT_SENSOR=0 -D DEBUG_RAW=0 upload_port = /dev/ttyUSB0 diff --git a/gateway/src/gateway.cpp b/gateway/src/gateway.cpp index defd07a..dd08f5a 100644 --- a/gateway/src/gateway.cpp +++ b/gateway/src/gateway.cpp @@ -4,41 +4,27 @@ #include "Protocol_1.h" #include "Protocol_2.h" #include "output.h" -#include - -#define RESET_PIN 10 -#define SEND_PIN 11 -#define RECEIVE_PIN 2 - -#if defined(ESP8266) -void onWifiConnected(); -#include -Scheduler ts; -Task tWifiConnected(TASK_IMMEDIATE, TASK_ONCE, onWifiConnected, &ts); - -#include "wifi.h" -#include "ota.h" -#endif RCSwitch mySwitch; -SerialReader<200> serialReader; +void runJsonCommand(char* cmd); + +#if defined(ESP8266) +#include "huzzah.h" +#else +#include "pro-mini.h" +#endif + void setup() { - digitalWrite(RESET_PIN, HIGH); pinMode(LED_BUILTIN, OUTPUT); - pinMode(RESET_PIN, OUTPUT); + Serial.begin(9600); mySwitch.enableReceive(digitalPinToInterrupt(RECEIVE_PIN)); mySwitch.enableTransmit(SEND_PIN); mySwitch.setRepeatTransmit(10); Dht::setup(); -#if defined(ESP8266) - Wifi::setup(); - Ota::setup(); -#endif - - Serial.begin(9600); + Board::setup(); delay(1000); } @@ -109,32 +95,8 @@ void runJsonCommand(char* cmd) { } } -void readCommand() { - if (serialReader.readLine(Serial) > 0) { - char* cmd = serialReader.getBuffer(); - if (strcmp("reset", cmd) == 0) { - Serial.println("resetting..."); - delay(1200); - digitalWrite(RESET_PIN, LOW); - Serial.println("resetting failed"); - } - runJsonCommand(cmd); - } -} - void loop() { - readCommand(); + Board::loop(); readRcSwitch(); Dht::read(); -#if defined(ESP8266) - ts.execute(); -#endif } - -#if defined(ESP8266) -void onWifiConnected() { - Serial.println("Wifi connected event"); - Wifi::printStatus(); - Ota::tLoop.enable(); -} -#endif \ No newline at end of file From cb53ad131ab7b16396c69549544c4ae9fe03f943 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Sat, 27 Apr 2024 13:31:51 +0200 Subject: [PATCH 04/61] remove unused blink --- gateway/include/pro-mini.h | 6 ------ gateway/src/gateway.cpp | 7 ------- 2 files changed, 13 deletions(-) diff --git a/gateway/include/pro-mini.h b/gateway/include/pro-mini.h index 6da5bb9..aacb484 100644 --- a/gateway/include/pro-mini.h +++ b/gateway/include/pro-mini.h @@ -12,12 +12,6 @@ namespace Board { pinMode(RESET_PIN, OUTPUT); } - void blink() { - digitalWrite(LED_BUILTIN, HIGH); - delay(200); - digitalWrite(LED_BUILTIN, LOW); - } - void readCommand() { if (serialReader.readLine(Serial) > 0) { char* cmd = serialReader.getBuffer(); diff --git a/gateway/src/gateway.cpp b/gateway/src/gateway.cpp index dd08f5a..b728d12 100644 --- a/gateway/src/gateway.cpp +++ b/gateway/src/gateway.cpp @@ -16,7 +16,6 @@ void runJsonCommand(char* cmd); void setup() { - pinMode(LED_BUILTIN, OUTPUT); Serial.begin(9600); mySwitch.enableReceive(digitalPinToInterrupt(RECEIVE_PIN)); @@ -29,12 +28,6 @@ void setup() { delay(1000); } -void blink() { - digitalWrite(LED_BUILTIN, HIGH); - delay(200); - digitalWrite(LED_BUILTIN, LOW); -} - Protocol* findProtocol(unsigned int protocol) { switch (protocol) { case 1: From 638f6b59394710dc8e8c940e5b8cf76c19539597 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Sat, 27 Apr 2024 16:06:59 +0200 Subject: [PATCH 05/61] cleanup wifi: - remove disconnected - not used - remove tConnected - duplicate of stationConnectedHandler --- gateway/include/wifi.h | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/gateway/include/wifi.h b/gateway/include/wifi.h index 0b9219e..cb48543 100644 --- a/gateway/include/wifi.h +++ b/gateway/include/wifi.h @@ -22,17 +22,13 @@ namespace Wifi { Serial.println("Reconnecting to WiFi netowrk..."); } }, &ts); - Task tConnected(TASK_IMMEDIATE, TASK_ONCE, [](){ - Serial.println("Wifi connected event"); - printStatus(); - Ota::tLoop.enable(); - }, &ts); void setup() { stationConnectedHandler = WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP& e) { - Serial.println("Reconnected to network."); - tConnected.restart(); + Serial.println("Connected to network."); + printStatus(); tReconnect.cancel(); + Ota::tLoop.enable(); }); stationDisconnectedHandler = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected& e) { @@ -54,12 +50,6 @@ namespace Wifi { currentPsk = WiFi.psk(); } - void disconnect() { - Serial.println("Disconnecting WiFi"); - WiFi.disconnect(); - WiFi.forceSleepBegin(); - } - void printStatus() { // print the SSID of the network you're attached to: Serial.print("SSID: "); From 6a2450d94fa36269aa1556c91cbe193ef51a5fb9 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Fri, 26 Apr 2024 14:30:41 +0200 Subject: [PATCH 06/61] dedicated reponse and error handling for each board --- gateway/include/huzzah.h | 6 ++++++ gateway/include/pro-mini.h | 12 ++++++++++++ gateway/src/gateway.cpp | 12 +++--------- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/gateway/include/huzzah.h b/gateway/include/huzzah.h index 431fcba..c800beb 100644 --- a/gateway/include/huzzah.h +++ b/gateway/include/huzzah.h @@ -26,4 +26,10 @@ namespace Board { void loop() { ts.execute(); } + + void publishResponse(JsonDocument& jsonDoc) { + } + + void handleJsonError(JsonDocument& jsonError) { + } } diff --git a/gateway/include/pro-mini.h b/gateway/include/pro-mini.h index aacb484..9283fea 100644 --- a/gateway/include/pro-mini.h +++ b/gateway/include/pro-mini.h @@ -1,4 +1,5 @@ #include +#include "output.h" #define RESET_PIN 10 #define SEND_PIN 11 @@ -10,6 +11,7 @@ namespace Board { void setup() { digitalWrite(RESET_PIN, HIGH); pinMode(RESET_PIN, OUTPUT); + Serial.begin(9600); } void readCommand() { @@ -28,4 +30,14 @@ namespace Board { void loop() { readCommand(); } + + void publishResponse(JsonDocument& jsonDoc) { + serializeJson(jsonDoc, Serial); + Serial.println(); + } + + void handleJsonError(JsonDocument& jsonError) { + serializeJson(jsonError, Serial); + Serial.println(); + } } diff --git a/gateway/src/gateway.cpp b/gateway/src/gateway.cpp index b728d12..7bd20e4 100644 --- a/gateway/src/gateway.cpp +++ b/gateway/src/gateway.cpp @@ -3,7 +3,6 @@ #include "Dht.h" #include "Protocol_1.h" #include "Protocol_2.h" -#include "output.h" RCSwitch mySwitch; void runJsonCommand(char* cmd); @@ -16,8 +15,6 @@ void runJsonCommand(char* cmd); void setup() { - Serial.begin(9600); - mySwitch.enableReceive(digitalPinToInterrupt(RECEIVE_PIN)); mySwitch.enableTransmit(SEND_PIN); mySwitch.setRepeatTransmit(10); @@ -55,8 +52,7 @@ void readRcSwitch() { p->toJson(value, jsonDoc); delete p; if (!jsonDoc.isNull()) { - serializeJson(jsonDoc, Serial); - Serial.println(); + Board::publishResponse(jsonDoc); } #endif } @@ -67,8 +63,7 @@ void handleJsonError(DeserializationError err, const char* cmd) { JsonObject error = jsonError.createNestedObject("error"); error["msg"] = err.c_str(); error["orig_cmd"] = cmd; - serializeJson(jsonError, Serial); - Serial.println(); + Board::handleJsonError(jsonError); } void runJsonCommand(char* cmd) { @@ -80,8 +75,7 @@ void runJsonCommand(char* cmd) { Protocol* p = findProtocol(rcSwitch["protocol"]); p->fromJson(rcSwitch, mySwitch); delete p; - serializeJson(jsonDoc, Serial); - Serial.println(); + Board::publishResponse(jsonDoc); } } else { handleJsonError(err, cmd); From 14a984195f2a73830fdbecc47606172189692276 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Mon, 29 Apr 2024 14:22:08 +0200 Subject: [PATCH 07/61] keep serial logic common RX pin does not work as RC recevier for huzzah --- gateway/include/huzzah.h | 9 +++++++++ gateway/include/pro-mini.h | 7 ------- gateway/src/gateway.cpp | 12 +++++++++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/gateway/include/huzzah.h b/gateway/include/huzzah.h index c800beb..9b6ef2a 100644 --- a/gateway/include/huzzah.h +++ b/gateway/include/huzzah.h @@ -10,6 +10,14 @@ Scheduler ts; #include "wifi.h" namespace Board { + + Task tReadCommand(TASK_IMMEDIATE, TASK_FOREVER, [](){ + if (serialReader.readLine(Serial) > 0) { + char* cmd = serialReader.getBuffer(); + runJsonCommand(cmd); + } + }, &ts); + void turnOffLed(uint8_t led) { digitalWrite(led, HIGH); } @@ -21,6 +29,7 @@ namespace Board { // turnOffLed(BLUE_LED); Wifi::setup(); Ota::setup(); + tReadCommand.enable(); } void loop() { diff --git a/gateway/include/pro-mini.h b/gateway/include/pro-mini.h index 9283fea..fed8722 100644 --- a/gateway/include/pro-mini.h +++ b/gateway/include/pro-mini.h @@ -1,4 +1,3 @@ -#include #include "output.h" #define RESET_PIN 10 @@ -6,12 +5,10 @@ #define RECEIVE_PIN 2 namespace Board { - SerialReader<200> serialReader; void setup() { digitalWrite(RESET_PIN, HIGH); pinMode(RESET_PIN, OUTPUT); - Serial.begin(9600); } void readCommand() { @@ -32,12 +29,8 @@ namespace Board { } void publishResponse(JsonDocument& jsonDoc) { - serializeJson(jsonDoc, Serial); - Serial.println(); } void handleJsonError(JsonDocument& jsonError) { - serializeJson(jsonError, Serial); - Serial.println(); } } diff --git a/gateway/src/gateway.cpp b/gateway/src/gateway.cpp index 7bd20e4..cdafa1d 100644 --- a/gateway/src/gateway.cpp +++ b/gateway/src/gateway.cpp @@ -1,10 +1,12 @@ #include #include +#include #include "Dht.h" #include "Protocol_1.h" #include "Protocol_2.h" RCSwitch mySwitch; +SerialReader<200> serialReader; void runJsonCommand(char* cmd); #if defined(ESP8266) @@ -19,6 +21,8 @@ void setup() { mySwitch.enableTransmit(SEND_PIN); mySwitch.setRepeatTransmit(10); + Serial.begin(9600); + Dht::setup(); Board::setup(); @@ -52,6 +56,8 @@ void readRcSwitch() { p->toJson(value, jsonDoc); delete p; if (!jsonDoc.isNull()) { + serializeJson(jsonDoc, Serial); + Serial.println(); Board::publishResponse(jsonDoc); } #endif @@ -63,6 +69,8 @@ void handleJsonError(DeserializationError err, const char* cmd) { JsonObject error = jsonError.createNestedObject("error"); error["msg"] = err.c_str(); error["orig_cmd"] = cmd; + serializeJson(jsonError, Serial); + Serial.println(); Board::handleJsonError(jsonError); } @@ -75,6 +83,8 @@ void runJsonCommand(char* cmd) { Protocol* p = findProtocol(rcSwitch["protocol"]); p->fromJson(rcSwitch, mySwitch); delete p; + serializeJson(jsonDoc, Serial); + Serial.println(); Board::publishResponse(jsonDoc); } } else { @@ -83,7 +93,7 @@ void runJsonCommand(char* cmd) { } void loop() { - Board::loop(); readRcSwitch(); + Board::loop(); Dht::read(); } From 3dcfc3e5ba968b33799edf96134326be1dffdcc9 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Tue, 30 Apr 2024 08:06:55 +0200 Subject: [PATCH 08/61] add mqtt with ha components configure Pollin switches --- gateway/include/Protocol_1.h | 7 ++ gateway/include/ha.h | 199 +++++++++++++++++++++++++++++++++++ gateway/include/huzzah.h | 33 +++++- gateway/include/mqtt.h | 126 ++++++++++++++++++++++ gateway/include/ota.h | 5 +- gateway/include/wifi.h | 4 +- gateway/platformio.ini | 3 +- 7 files changed, 371 insertions(+), 6 deletions(-) create mode 100644 gateway/include/ha.h create mode 100644 gateway/include/mqtt.h diff --git a/gateway/include/Protocol_1.h b/gateway/include/Protocol_1.h index 935904e..ccbc329 100644 --- a/gateway/include/Protocol_1.h +++ b/gateway/include/Protocol_1.h @@ -26,4 +26,11 @@ public: rcSwitch["channel"] = decoder.device; rcSwitch["raw_value"] = value; } + + static char* buildId(const char* group, const unsigned char channel) { + char* uId = new char[30]; + sprintf(uId, "%s_%d", group, channel); + return uId; + } + }; diff --git a/gateway/include/ha.h b/gateway/include/ha.h new file mode 100644 index 0000000..4879828 --- /dev/null +++ b/gateway/include/ha.h @@ -0,0 +1,199 @@ +#pragma once + +#include + +#define JSON_SIZE 512 +#define TOPIC_SIZE 255 +#define DEVICE_ID "rc-gateway" + +namespace Ha { + + struct Component { + const char* name; + char* id; + const char* type; + char configTopic[TOPIC_SIZE]; + + Component(const char* name, const char* id, const char* type) : name(name), id((char*)id), type(type) { + sprintf(configTopic, "homeassistant/%s/rc-gateway/%s/config", type, id); + } + + virtual void buildUniqueId(char* uniqueId) = 0; + + virtual void buildConfig(JsonDocument& jsonDoc) { + buildDeviceConfig(jsonDoc); + jsonDoc["name"] = name; + char uniqueId[50]; + buildUniqueId(uniqueId); + jsonDoc["unique_id"] = uniqueId; + } + + virtual void buildDeviceConfig(JsonDocument& jsonDoc) { + JsonObject device = jsonDoc.createNestedObject("device"); + device["name"] = "RC Gateway"; + device["model"] = "Huzzah Esp8266"; + device["manufacturer"] = "Adafruit"; + JsonArray connections = device.createNestedArray("connections"); + JsonArray mac = connections.createNestedArray(); + mac.add("mac"); + mac.add(WiFi.macAddress()); + JsonArray identifiers = device.createNestedArray("identifiers"); + identifiers.add(DEVICE_ID); + } + }; + + struct Command : Component { + char commandTopic[TOPIC_SIZE]; + + Command(const char* name, const char* id, const char* type) : Component(name, id, type) { + } + + void buildUniqueId(char* uniqueId) override { + sprintf(uniqueId, "rc_gateway_%s", id); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Component::buildConfig(jsonDoc); + jsonDoc["command_topic"] = commandTopic; + } + + }; + + typedef void (*onMessage)(const char* msg); + struct Button : Command { + static constexpr const char* type = "button"; + onMessage f; + + Button(const char* name, const char* id, onMessage f) : Command(name, id, type), f(f) { + sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s", type, id); + } + + }; + + struct Switch : Command { + static constexpr const char* type = "switch"; + char stateTopic[TOPIC_SIZE]; + const char* area; + uint16_t (*publisher)(const char* topic, const char* message); + virtual void onCommand(const char* msg){} + + Switch(const char* name, const char* id, const char* area, uint16_t (*publisher)(const char* topic, const char* message) = nullptr) + : Command(name, id, type), area(area), publisher(publisher) { + sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); + } + + void buildDeviceConfig(JsonDocument& jsonDoc) override { + JsonObject device = jsonDoc.createNestedObject("device"); + device["name"] = name; + device["via_device"] = DEVICE_ID; + device["suggested_area"] = area; + JsonArray identifiers = device.createNestedArray("identifiers"); + identifiers.add(id); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Command::buildConfig(jsonDoc); + jsonDoc["name"] = nullptr; + jsonDoc["device_class"] = "outlet"; + jsonDoc["retain"] = true; + if (stateTopic[0]) jsonDoc["state_topic"] = stateTopic; + } + + Switch* withStateTopic() { + sprintf(stateTopic, "homeassistant/%s/rc-gateway/%s/state", type, id); + return this; + } + + void publishState(bool state) { + publisher(stateTopic, state ? "ON" : "OFF"); + } + + }; + + struct PollinSwitch : Switch { + const char* group; + unsigned char channel; + + PollinSwitch(const char* name, const char* area, const char* group, const unsigned char channel, uint16_t (*publisher)(const char* topic, const char* message)) + : Switch(name, Protocol_1::buildId(group, channel), area, publisher), group(group), channel(channel) { + sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); + } + + void onCommand(const char* msg) override { + (String{ "ON" }.equals(msg)) ? mySwitch.switchOn(group, channel) : mySwitch.switchOff(group, channel); + publisher(stateTopic, msg); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Switch::buildConfig(jsonDoc); + JsonObject device = jsonDoc["device"]; + device["manufacturer"] = "Pollin"; + } + + }; + + struct Sensor : Component { + const char* deviceClass; + const char* unitMeasure; + const char* valueTemplate; + static constexpr const char* stateTopic = "homeassistant/sensor/rc-gateway/state"; + + Sensor(const char* name, const char* id) : Component(name, id, "sensor") { + } + + void buildUniqueId(char* uniqueId) override { + sprintf(uniqueId, "living_room_%s", id); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Component::buildConfig(jsonDoc); + jsonDoc["device_class"] = deviceClass; + jsonDoc["unit_of_measurement"] = unitMeasure; + jsonDoc["value_template"] = valueTemplate; + jsonDoc["state_topic"] = stateTopic; + } + + void buildDeviceConfig(JsonDocument& jsonDoc) override { + JsonObject device = jsonDoc.createNestedObject("device"); + device["name"] = "Living room"; + device["model"] = "BPM280"; + device["suggested_area"] = "Living room"; + device["via_device"] = "rc-gateway"; + JsonArray identifiers = device.createNestedArray("identifiers"); + identifiers.add("esp-clock-living-room"); + } + + }; + + struct TemperatureSensor : Sensor { + TemperatureSensor(const char* name, const char* id) : Sensor(name, id) { + deviceClass = "temperature"; + unitMeasure = "°C"; + valueTemplate = "{{ value_json.temperature }}"; + } + }; + + struct HumiditySensor : Sensor { + HumiditySensor(const char* name, const char* id) : Sensor(name, id) { + deviceClass = "humidity"; + unitMeasure = "%"; + valueTemplate = "{{ value_json.humidity }}"; + } + }; + + struct PressureSensor : Sensor { + PressureSensor(const char* name, const char* id) : Sensor(name, id) { + deviceClass = "pressure"; + unitMeasure = "hPa"; + valueTemplate = "{{ value_json.pressure }}"; + } + }; + + struct AltitudeSensor : Sensor { + AltitudeSensor(const char* name, const char* id) : Sensor(name, id) { + deviceClass = "distance"; + unitMeasure = "m"; + valueTemplate = "{{ value_json.altitude }}"; + } + }; +} diff --git a/gateway/include/huzzah.h b/gateway/include/huzzah.h index 9b6ef2a..fafbea5 100644 --- a/gateway/include/huzzah.h +++ b/gateway/include/huzzah.h @@ -1,10 +1,12 @@ #include -#define SEND_PIN PIN_SPI_MOSI -#define RECEIVE_PIN PIN_SPI_MISO +#define SEND_PIN 12 +#define RECEIVE_PIN 13 #define RED_LED LED_BUILTIN // #define BLUE_LED 2 +using namespace std; + Scheduler ts; #include "wifi.h" @@ -23,12 +25,14 @@ namespace Board { } void setup() { + // Serial.begin(9600, SERIAL_8N1, SERIAL_TX_ONLY); pinMode(RED_LED, OUTPUT); turnOffLed(RED_LED); // pinMode(BLUE_LED, OUTPUT); // turnOffLed(BLUE_LED); Wifi::setup(); Ota::setup(); + Mqtt::setup(); tReadCommand.enable(); } @@ -37,8 +41,33 @@ namespace Board { } void publishResponse(JsonDocument& jsonDoc) { + char message[255]; + serializeJson(jsonDoc, message); + Mqtt::publish("homeassistant/sensor/rc-gateway/raw", message); + if (jsonDoc.containsKey("rcSwitch")) { + JsonObjectConst rcSwitch = jsonDoc["rcSwitch"]; + string id; + switch ((unsigned int)rcSwitch["protocol"]) { + case 1: + // buildId returns a new pointer, should it be deleted, or string will take care of it? + id = Protocol_1::buildId((const char*)rcSwitch["group"], (int)rcSwitch["channel"]); + break; + case 2: + break; + default: + break; + } + Ha::Switch* el = Mqtt::mapSwitches[id]; + if (el != nullptr) { + el->publishState((bool)rcSwitch["state"]); + } + } } void handleJsonError(JsonDocument& jsonError) { + char message[255]; + serializeJson(jsonError, message); + Mqtt::publish("homeassistant/sensor/rc-gateway/raw", message); } } +// {"rcSwitch":{"protocol":1,"state":false,"group":"11111","channel":4}} \ No newline at end of file diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h new file mode 100644 index 0000000..40a3813 --- /dev/null +++ b/gateway/include/mqtt.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include "ha.h" + + +#define MQTT_HOST IPAddress(192, 168, 5, 11) +#define MQTT_PORT 1883 + +namespace Mqtt { + + AsyncMqttClient client; + + void publishInit(); + void publishBme280(); + void disconnect(); + Task tReConnect(5 * TASK_MINUTE, TASK_FOREVER, []() { + Serial.println("Connecting to MQTT..."); + client.connect(); + }, &ts); + Task tPublishInit(TASK_IMMEDIATE, TASK_ONCE, publishInit, &ts); + + const char* mainTopic = "homeassistant/+/rc-gateway/#"; + + void disconnect() { + client.unsubscribe(mainTopic); + client.disconnect(); + } + + uint16_t publish(const char* topic, const char* message) { + return client.publish(topic, 0, true, message); + } + + Ha::Sensor* sensors[] = { + // new Ha::TemperatureSensor{"Temperature", "temperature"}, + // new Ha::HumiditySensor{"Humidity", "humidity"}, + // new Ha::PressureSensor{"Pressure", "pressure"}, + // new Ha::AltitudeSensor{"Altitude", "altitude"} + }; + + Ha::Button* buttons[] = { + new Ha::Button{"Restart", "restart", + [](const char* msg) { + if (String { "PRESS" }.equals(msg)) ESP.restart(); + } + } + }; + + Ha::Switch* switches[] = { + (new Ha::PollinSwitch{"Meeting sensor", "Dining room", "00001", 1, publish})->withStateTopic(), + (new Ha::PollinSwitch{"Fire Tv", "Living room", "00001", 2, publish})->withStateTopic(), + (new Ha::PollinSwitch{"Diningroom player", "Dining room", "00001", 3, publish})->withStateTopic(), + (new Ha::PollinSwitch{"Train", "Playroom", "11111", 4, publish})->withStateTopic() + }; + + unordered_map mapSwitches; + + void publishComponentConfig(Ha::Component& component) { + StaticJsonDocument jsonDoc; + component.buildConfig(jsonDoc); + + char message[JSON_SIZE]; + serializeJson(jsonDoc, message); + + publish(component.configTopic, message); + } + + void publishInit() { + // for (Ha::Component* cmp : sensors) { + // publishComponentConfig(*cmp); + // } + for (Ha::Component* cmp : buttons) { + publishComponentConfig(*cmp); + } + for (Ha::Switch* cmp : switches) { + mapSwitches.insert({string(cmp->id), cmp}); + publishComponentConfig(*cmp); + } + ts.deleteTask(tPublishInit); + } + + void publishBme280() { + // StaticJsonDocument<255> jsonDoc; + // jsonDoc["temperature"] = Bme::data.temp; + // jsonDoc["humidity"] = Bme::data.humidity; + // jsonDoc["pressure"] = Bme::data.pressure; + // jsonDoc["altitude"] = Bme::data.altitude; + // char message[255]; + // serializeJson(jsonDoc, message); + // publish(Ha::Sensor::stateTopic, message); + } + + void onMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + char msg[len + 1]; + memcpy(msg, payload, len); + msg[len] = 0; + for (Ha::Button* cmd : buttons) { + if (String{ cmd->commandTopic }.equals(topic) && cmd->f != nullptr) { + cmd->f(msg); + return; + } + } + for (Ha::Switch* cmd : switches) { + if (String{ cmd->commandTopic }.equals(topic)) { + cmd->onCommand(msg); + return; + } + } + } + + void setup() { + client.onConnect([](bool sessionPresent) { + tPublishInit.enable(); + client.subscribe(mainTopic, 0); + tReConnect.disable(); + Serial.println("Connected to MQTT"); + }); + client.onDisconnect([](AsyncMqttClientDisconnectReason reason) { + tReConnect.enableDelayed(); + Serial.println("Disconnected from MQTT"); + }); + client.onMessage(onMessage); + client.setServer(MQTT_HOST, MQTT_PORT); + } +} diff --git a/gateway/include/ota.h b/gateway/include/ota.h index 6015b5c..eb7a37d 100644 --- a/gateway/include/ota.h +++ b/gateway/include/ota.h @@ -10,10 +10,11 @@ namespace Ota { void setup() { ArduinoOTA.onStart([]() { - Serial.println("Start"); + Serial.println("Starting OTA"); + Mqtt::disconnect(); }); ArduinoOTA.onEnd([]() { - Serial.println("\nEnd"); + Serial.println("\nOTA Finished"); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); diff --git a/gateway/include/wifi.h b/gateway/include/wifi.h index cb48543..2f0c877 100644 --- a/gateway/include/wifi.h +++ b/gateway/include/wifi.h @@ -1,6 +1,7 @@ #include #include #include +#include "mqtt.h" #include "ota.h" #include "credentials.h" @@ -29,6 +30,7 @@ namespace Wifi { printStatus(); tReconnect.cancel(); Ota::tLoop.enable(); + Mqtt::tReConnect.restart(); }); stationDisconnectedHandler = WiFi.onStationModeDisconnected([](const WiFiEventStationModeDisconnected& e) { @@ -41,11 +43,11 @@ namespace Wifi { wifiMulti.addAP(credentials[i].ssid, credentials[i].password); } + WiFi.setHostname("rc-gateway"); Serial.println("Connecting to WiFi netowrk."); while (wifiMulti.run() != WL_CONNECTED) { delay(500); } - WiFi.setHostname("rc-gateway"); currentSSID = WiFi.SSID(); currentPsk = WiFi.psk(); } diff --git a/gateway/platformio.ini b/gateway/platformio.ini index 3faf2e2..8f659c4 100644 --- a/gateway/platformio.ini +++ b/gateway/platformio.ini @@ -21,8 +21,9 @@ lib_deps = sui77/rc-switch@^2.6.4 bblanchon/ArduinoJson@6.21.5 adafruit/Adafruit Unified Sensor@^1.1.4 - arkhipenko/TaskScheduler@^3.7.0 adafruit/DHT sensor library@1.3.2 + arkhipenko/TaskScheduler@^3.7.0 + marvinroger/AsyncMqttClient@^0.9.0 https://git.hodos.ro/arduino/lib_serial-reader.git@^1.0.0 build_flags = -D DHT_SENSOR=0 -D DEBUG_RAW=0 check_tool = cppcheck From 41e2e1359a42b7e371efbe42a1c502c65033a68b Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Tue, 30 Apr 2024 08:45:50 +0200 Subject: [PATCH 09/61] make area optional and add EasyHome switches --- gateway/include/ha.h | 35 +++++++++++++++++++++++++++++++---- gateway/include/mqtt.h | 9 +++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/gateway/include/ha.h b/gateway/include/ha.h index 4879828..381f520 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -77,8 +77,8 @@ namespace Ha { uint16_t (*publisher)(const char* topic, const char* message); virtual void onCommand(const char* msg){} - Switch(const char* name, const char* id, const char* area, uint16_t (*publisher)(const char* topic, const char* message) = nullptr) - : Command(name, id, type), area(area), publisher(publisher) { + Switch(const char* name, const char* id, uint16_t (*publisher)(const char* topic, const char* message) = nullptr) + : Command(name, id, type), publisher(publisher) { sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); } @@ -104,6 +104,11 @@ namespace Ha { return this; } + Switch* withArea(const char* area) { + this->area = area; + return this; + } + void publishState(bool state) { publisher(stateTopic, state ? "ON" : "OFF"); } @@ -114,8 +119,8 @@ namespace Ha { const char* group; unsigned char channel; - PollinSwitch(const char* name, const char* area, const char* group, const unsigned char channel, uint16_t (*publisher)(const char* topic, const char* message)) - : Switch(name, Protocol_1::buildId(group, channel), area, publisher), group(group), channel(channel) { + PollinSwitch(const char* name, const char* group, const unsigned char channel, uint16_t (*publisher)(const char* topic, const char* message)) + : Switch(name, Protocol_1::buildId(group, channel), publisher), group(group), channel(channel) { sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); } @@ -132,6 +137,28 @@ namespace Ha { }; + struct EasyHomeSwitch : Switch { + const char* on; + const char* off; + + EasyHomeSwitch(const char* name, const char* id, const char* on, const char* off) : Switch(name, id), on(on), off(off) { + sprintf(commandTopic, "homeassistant/%s/rc-gateway/easy_home_%s/set", type, id); + } + + void onCommand(const char* msg) override { + mySwitch.setProtocol(4); + String{ "ON" }.equals(msg) ? mySwitch.send(on) : mySwitch.send(off); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Switch::buildConfig(jsonDoc); + JsonObject device = jsonDoc["device"]; + device["manufacturer"] = "Intertek"; + device["model"] = "Easy Home"; + } + + }; + struct Sensor : Component { const char* deviceClass; const char* unitMeasure; diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h index 40a3813..45a4423 100644 --- a/gateway/include/mqtt.h +++ b/gateway/include/mqtt.h @@ -48,10 +48,11 @@ namespace Mqtt { }; Ha::Switch* switches[] = { - (new Ha::PollinSwitch{"Meeting sensor", "Dining room", "00001", 1, publish})->withStateTopic(), - (new Ha::PollinSwitch{"Fire Tv", "Living room", "00001", 2, publish})->withStateTopic(), - (new Ha::PollinSwitch{"Diningroom player", "Dining room", "00001", 3, publish})->withStateTopic(), - (new Ha::PollinSwitch{"Train", "Playroom", "11111", 4, publish})->withStateTopic() + (new Ha::PollinSwitch{"Meeting sensor", "00001", 1, publish})->withArea("Dining room")->withStateTopic(), + (new Ha::PollinSwitch{"Fire Tv", "00001", 2, publish})->withArea("Living room")->withStateTopic(), + (new Ha::PollinSwitch{"Diningroom player", "00001", 3, publish})->withArea("Dining room")->withStateTopic(), + (new Ha::PollinSwitch{"Train", "11111", 4, publish})->withArea("Playroom")->withStateTopic(), + (new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", "010001101001100101110000", "010010111110000010100000"})->withArea("Basement") }; unordered_map mapSwitches; From 9611b2c56408451175e8de3e30ea48e60575f426 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Tue, 30 Apr 2024 16:22:55 +0200 Subject: [PATCH 10/61] support state for EasyHome switches --- gateway/include/ha.h | 20 +++++++++++--------- gateway/include/huzzah.h | 30 ++++++++++++++++++++---------- gateway/include/mqtt.h | 24 ++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/gateway/include/ha.h b/gateway/include/ha.h index 381f520..2a7fbda 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -70,14 +70,15 @@ namespace Ha { }; + typedef uint16_t (*publisherFunc)(const char* topic, const char* message); struct Switch : Command { static constexpr const char* type = "switch"; char stateTopic[TOPIC_SIZE]; const char* area; - uint16_t (*publisher)(const char* topic, const char* message); + publisherFunc publisher; virtual void onCommand(const char* msg){} - Switch(const char* name, const char* id, uint16_t (*publisher)(const char* topic, const char* message) = nullptr) + Switch(const char* name, const char* id, publisherFunc publisher = nullptr) : Command(name, id, type), publisher(publisher) { sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); } @@ -119,7 +120,7 @@ namespace Ha { const char* group; unsigned char channel; - PollinSwitch(const char* name, const char* group, const unsigned char channel, uint16_t (*publisher)(const char* topic, const char* message)) + PollinSwitch(const char* name, const char* group, const unsigned char channel, publisherFunc publisher) : Switch(name, Protocol_1::buildId(group, channel), publisher), group(group), channel(channel) { sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); } @@ -138,16 +139,18 @@ namespace Ha { }; struct EasyHomeSwitch : Switch { - const char* on; - const char* off; + unsigned long *on; + unsigned long *off; - EasyHomeSwitch(const char* name, const char* id, const char* on, const char* off) : Switch(name, id), on(on), off(off) { - sprintf(commandTopic, "homeassistant/%s/rc-gateway/easy_home_%s/set", type, id); + EasyHomeSwitch(const char* name, const char* id, unsigned long on[4], unsigned long off[4], publisherFunc publisher) + : Switch(name, id, publisher), on(on), off(off) { + sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); } void onCommand(const char* msg) override { mySwitch.setProtocol(4); - String{ "ON" }.equals(msg) ? mySwitch.send(on) : mySwitch.send(off); + String{ "ON" }.equals(msg) ? mySwitch.send(on[0], 24) : mySwitch.send(off[0], 24); + publisher(stateTopic, msg); } void buildConfig(JsonDocument& jsonDoc) override { @@ -156,7 +159,6 @@ namespace Ha { device["manufacturer"] = "Intertek"; device["model"] = "Easy Home"; } - }; struct Sensor : Component { diff --git a/gateway/include/huzzah.h b/gateway/include/huzzah.h index fafbea5..6cba31f 100644 --- a/gateway/include/huzzah.h +++ b/gateway/include/huzzah.h @@ -46,20 +46,30 @@ namespace Board { Mqtt::publish("homeassistant/sensor/rc-gateway/raw", message); if (jsonDoc.containsKey("rcSwitch")) { JsonObjectConst rcSwitch = jsonDoc["rcSwitch"]; - string id; switch ((unsigned int)rcSwitch["protocol"]) { - case 1: + case 1: { // buildId returns a new pointer, should it be deleted, or string will take care of it? - id = Protocol_1::buildId((const char*)rcSwitch["group"], (int)rcSwitch["channel"]); - break; - case 2: - break; - default: + string id = Protocol_1::buildId((const char*)rcSwitch["group"], (int)rcSwitch["channel"]); + Ha::Switch* el = Mqtt::mapSwitches[id]; + if (el != nullptr) { + el->publishState((bool)rcSwitch["state"]); + } break; } - Ha::Switch* el = Mqtt::mapSwitches[id]; - if (el != nullptr) { - el->publishState((bool)rcSwitch["state"]); + case 2: + break; + default: { + unsigned long value = rcSwitch["value"]; + Ha::Switch* aSwitch = Mqtt::onSwitches[value]; + if (aSwitch != nullptr) { + aSwitch->publishState(true); + } else { + aSwitch = Mqtt::offSwitches[value]; + if (aSwitch != nullptr) { + aSwitch->publishState(false); + } + } + } } } } diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h index 45a4423..4ce061d 100644 --- a/gateway/include/mqtt.h +++ b/gateway/include/mqtt.h @@ -51,11 +51,18 @@ namespace Mqtt { (new Ha::PollinSwitch{"Meeting sensor", "00001", 1, publish})->withArea("Dining room")->withStateTopic(), (new Ha::PollinSwitch{"Fire Tv", "00001", 2, publish})->withArea("Living room")->withStateTopic(), (new Ha::PollinSwitch{"Diningroom player", "00001", 3, publish})->withArea("Dining room")->withStateTopic(), - (new Ha::PollinSwitch{"Train", "11111", 4, publish})->withArea("Playroom")->withStateTopic(), - (new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", "010001101001100101110000", "010010111110000010100000"})->withArea("Basement") + (new Ha::PollinSwitch{"Train", "11111", 4, publish})->withArea("Playroom")->withStateTopic() + }; + + Ha::EasyHomeSwitch* fritz = new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]){4483136, 4626800, 4819632, 4661552}, (unsigned long[4]){4767520, 4537104, 4326544, 4972704}, publish}; + + Ha::EasyHomeSwitch* otherSwitches[] = { + fritz }; unordered_map mapSwitches; + unordered_map onSwitches; + unordered_map offSwitches; void publishComponentConfig(Ha::Component& component) { StaticJsonDocument jsonDoc; @@ -71,6 +78,8 @@ namespace Mqtt { // for (Ha::Component* cmp : sensors) { // publishComponentConfig(*cmp); // } + fritz->withArea("Basement"); + fritz->withStateTopic(); for (Ha::Component* cmp : buttons) { publishComponentConfig(*cmp); } @@ -78,6 +87,11 @@ namespace Mqtt { mapSwitches.insert({string(cmp->id), cmp}); publishComponentConfig(*cmp); } + for (Ha::EasyHomeSwitch* cmp : otherSwitches) { + for (int i = 0; i < 4; i++) onSwitches.insert({cmp->on[i], cmp}); + for (int i = 0; i < 4; i++) offSwitches.insert({cmp->off[i], cmp}); + publishComponentConfig(*cmp); + } ts.deleteTask(tPublishInit); } @@ -108,6 +122,12 @@ namespace Mqtt { return; } } + for (Ha::Switch* cmd : otherSwitches) { + if (String{ cmd->commandTopic }.equals(topic)) { + cmd->onCommand(msg); + return; + } + } } void setup() { From aaeb0a85e607112a0e30fae40fec18b9202f1a68 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Tue, 30 Apr 2024 16:23:54 +0200 Subject: [PATCH 11/61] add support for reacting on switching on/off all EasyHome switches --- gateway/include/ha.h | 28 +++++++++++++++++++++++++--- gateway/include/huzzah.h | 17 ++++++++--------- gateway/include/mqtt.h | 13 ++++++------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/gateway/include/ha.h b/gateway/include/ha.h index 2a7fbda..41d7694 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -114,6 +114,26 @@ namespace Ha { publisher(stateTopic, state ? "ON" : "OFF"); } + template + struct Builder { + T* t; + + Builder(T* t) : t(t) {} + + T* build() { + return t; + } + + Builder* withArea(const char* area) { + t->withArea(area); + return this; + } + + Builder* withStateTopic() { + t->withStateTopic(); + return this; + } + }; }; struct PollinSwitch : Switch { @@ -139,11 +159,13 @@ namespace Ha { }; struct EasyHomeSwitch : Switch { - unsigned long *on; - unsigned long *off; + unsigned long on[8] = {4972714, 4767530, 4537114, 4326554}; + unsigned long off[8] = {4483146, 4626810, 4819642, 4661562}; EasyHomeSwitch(const char* name, const char* id, unsigned long on[4], unsigned long off[4], publisherFunc publisher) - : Switch(name, id, publisher), on(on), off(off) { + : Switch(name, id, publisher) { + memccpy(&this->on[4], on, 4, sizeof(unsigned long)); + memccpy(&this->off[4], off, 4, sizeof(unsigned long)); sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); } diff --git a/gateway/include/huzzah.h b/gateway/include/huzzah.h index 6cba31f..29ee4e3 100644 --- a/gateway/include/huzzah.h +++ b/gateway/include/huzzah.h @@ -60,15 +60,14 @@ namespace Board { break; default: { unsigned long value = rcSwitch["value"]; - Ha::Switch* aSwitch = Mqtt::onSwitches[value]; - if (aSwitch != nullptr) { - aSwitch->publishState(true); - } else { - aSwitch = Mqtt::offSwitches[value]; - if (aSwitch != nullptr) { - aSwitch->publishState(false); - } - } + auto range = Mqtt::onSwitches.equal_range(value); + for_each(range.first, range.second, [](unordered_multimap::value_type& x){ + x.second->publishState(true); + }); + range = Mqtt::offSwitches.equal_range(value); + for_each(range.first, range.second, [](unordered_multimap::value_type& x){ + x.second->publishState(false); + }); } } } diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h index 4ce061d..4731fed 100644 --- a/gateway/include/mqtt.h +++ b/gateway/include/mqtt.h @@ -54,15 +54,16 @@ namespace Mqtt { (new Ha::PollinSwitch{"Train", "11111", 4, publish})->withArea("Playroom")->withStateTopic() }; - Ha::EasyHomeSwitch* fritz = new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]){4483136, 4626800, 4819632, 4661552}, (unsigned long[4]){4767520, 4537104, 4326544, 4972704}, publish}; - Ha::EasyHomeSwitch* otherSwitches[] = { - fritz + (Ha::Switch::Builder{new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4819632, 4661552 }, (unsigned long[4]) { 4767520, 4537104, 4326544, 4972704 }, publish}}) + .withArea("Basement")->withStateTopic()->build(), + (Ha::Switch::Builder{new Ha::EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4661556, 4819636, 4626804, 4483140 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }, publish}}) + .withArea("Basement")->withStateTopic()->build() }; unordered_map mapSwitches; - unordered_map onSwitches; - unordered_map offSwitches; + unordered_multimap onSwitches; + unordered_map offSwitches; void publishComponentConfig(Ha::Component& component) { StaticJsonDocument jsonDoc; @@ -78,8 +79,6 @@ namespace Mqtt { // for (Ha::Component* cmp : sensors) { // publishComponentConfig(*cmp); // } - fritz->withArea("Basement"); - fritz->withStateTopic(); for (Ha::Component* cmp : buttons) { publishComponentConfig(*cmp); } From a9d66e29e3916d4535f28832ff3beadff00a6bd9 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Tue, 30 Apr 2024 21:17:33 +0200 Subject: [PATCH 12/61] fix on/off all switches --- gateway/include/ha.h | 10 +++++----- gateway/include/mqtt.h | 12 +++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/gateway/include/ha.h b/gateway/include/ha.h index 41d7694..2079534 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -159,19 +159,19 @@ namespace Ha { }; struct EasyHomeSwitch : Switch { - unsigned long on[8] = {4972714, 4767530, 4537114, 4326554}; - unsigned long off[8] = {4483146, 4626810, 4819642, 4661562}; + unsigned long on[8] = {4326554, 4537114, 4767530, 4972714}; + unsigned long off[8] = {4483146, 4626810, 4661562, 4819642}; EasyHomeSwitch(const char* name, const char* id, unsigned long on[4], unsigned long off[4], publisherFunc publisher) : Switch(name, id, publisher) { - memccpy(&this->on[4], on, 4, sizeof(unsigned long)); - memccpy(&this->off[4], off, 4, sizeof(unsigned long)); + memcpy(&this->on[4], on, 4*sizeof(unsigned long)); + memcpy(&this->off[4], off, 4*sizeof(unsigned long)); sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); } void onCommand(const char* msg) override { mySwitch.setProtocol(4); - String{ "ON" }.equals(msg) ? mySwitch.send(on[0], 24) : mySwitch.send(off[0], 24); + String{ "ON" }.equals(msg) ? mySwitch.send(on[4], 24) : mySwitch.send(off[4], 24); publisher(stateTopic, msg); } diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h index 4731fed..a70ef9c 100644 --- a/gateway/include/mqtt.h +++ b/gateway/include/mqtt.h @@ -55,15 +55,15 @@ namespace Mqtt { }; Ha::EasyHomeSwitch* otherSwitches[] = { - (Ha::Switch::Builder{new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4819632, 4661552 }, (unsigned long[4]) { 4767520, 4537104, 4326544, 4972704 }, publish}}) + (Ha::Switch::Builder{new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }, publish}}) .withArea("Basement")->withStateTopic()->build(), - (Ha::Switch::Builder{new Ha::EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4661556, 4819636, 4626804, 4483140 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }, publish}}) + (Ha::Switch::Builder{new Ha::EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }, publish}}) .withArea("Basement")->withStateTopic()->build() }; unordered_map mapSwitches; unordered_multimap onSwitches; - unordered_map offSwitches; + unordered_multimap offSwitches; void publishComponentConfig(Ha::Component& component) { StaticJsonDocument jsonDoc; @@ -87,8 +87,10 @@ namespace Mqtt { publishComponentConfig(*cmp); } for (Ha::EasyHomeSwitch* cmp : otherSwitches) { - for (int i = 0; i < 4; i++) onSwitches.insert({cmp->on[i], cmp}); - for (int i = 0; i < 4; i++) offSwitches.insert({cmp->off[i], cmp}); + for (int i = 0; i < 8; i++) { + onSwitches.insert({cmp->on[i], cmp}); + offSwitches.insert({cmp->off[i], cmp}); + } publishComponentConfig(*cmp); } ts.deleteTask(tPublishInit); From 6b37d61b5cf655b295110990e7c76f3b2a893d7a Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Tue, 30 Apr 2024 23:08:22 +0200 Subject: [PATCH 13/61] unify switches under same list and get rid of Builder --- gateway/include/ha.h | 34 +++++++++++++--------------------- gateway/include/huzzah.h | 18 +++++++++++++----- gateway/include/mqtt.h | 30 ++++-------------------------- 3 files changed, 30 insertions(+), 52 deletions(-) diff --git a/gateway/include/ha.h b/gateway/include/ha.h index 2079534..62fb34d 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -76,7 +76,9 @@ namespace Ha { char stateTopic[TOPIC_SIZE]; const char* area; publisherFunc publisher; + virtual void onCommand(const char* msg){} + virtual void addToMap(){} Switch(const char* name, const char* id, publisherFunc publisher = nullptr) : Command(name, id, type), publisher(publisher) { @@ -113,27 +115,6 @@ namespace Ha { void publishState(bool state) { publisher(stateTopic, state ? "ON" : "OFF"); } - - template - struct Builder { - T* t; - - Builder(T* t) : t(t) {} - - T* build() { - return t; - } - - Builder* withArea(const char* area) { - t->withArea(area); - return this; - } - - Builder* withStateTopic() { - t->withStateTopic(); - return this; - } - }; }; struct PollinSwitch : Switch { @@ -156,6 +137,10 @@ namespace Ha { device["manufacturer"] = "Pollin"; } + void addToMap() override { + p1Switches.insert({string(id), this}); + } + }; struct EasyHomeSwitch : Switch { @@ -181,6 +166,13 @@ namespace Ha { device["manufacturer"] = "Intertek"; device["model"] = "Easy Home"; } + + void addToMap() override { + for (int i = 0; i < 8; i++) { + onSwitches.insert({on[i], this}); + offSwitches.insert({off[i], this}); + } + } }; struct Sensor : Component { diff --git a/gateway/include/huzzah.h b/gateway/include/huzzah.h index 29ee4e3..8fbfd02 100644 --- a/gateway/include/huzzah.h +++ b/gateway/include/huzzah.h @@ -9,6 +9,14 @@ using namespace std; Scheduler ts; +namespace Ha { + struct Switch; +} +typedef unordered_multimap mapswitches; +mapswitches onSwitches; +mapswitches offSwitches; +unordered_map p1Switches; + #include "wifi.h" namespace Board { @@ -50,7 +58,7 @@ namespace Board { case 1: { // buildId returns a new pointer, should it be deleted, or string will take care of it? string id = Protocol_1::buildId((const char*)rcSwitch["group"], (int)rcSwitch["channel"]); - Ha::Switch* el = Mqtt::mapSwitches[id]; + Ha::Switch* el = p1Switches[id]; if (el != nullptr) { el->publishState((bool)rcSwitch["state"]); } @@ -60,12 +68,12 @@ namespace Board { break; default: { unsigned long value = rcSwitch["value"]; - auto range = Mqtt::onSwitches.equal_range(value); - for_each(range.first, range.second, [](unordered_multimap::value_type& x){ + auto range = onSwitches.equal_range(value); + for_each(range.first, range.second, [](mapswitches::value_type& x){ x.second->publishState(true); }); - range = Mqtt::offSwitches.equal_range(value); - for_each(range.first, range.second, [](unordered_multimap::value_type& x){ + range = offSwitches.equal_range(value); + for_each(range.first, range.second, [](mapswitches::value_type& x){ x.second->publishState(false); }); } diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h index a70ef9c..ccb4d84 100644 --- a/gateway/include/mqtt.h +++ b/gateway/include/mqtt.h @@ -51,20 +51,11 @@ namespace Mqtt { (new Ha::PollinSwitch{"Meeting sensor", "00001", 1, publish})->withArea("Dining room")->withStateTopic(), (new Ha::PollinSwitch{"Fire Tv", "00001", 2, publish})->withArea("Living room")->withStateTopic(), (new Ha::PollinSwitch{"Diningroom player", "00001", 3, publish})->withArea("Dining room")->withStateTopic(), - (new Ha::PollinSwitch{"Train", "11111", 4, publish})->withArea("Playroom")->withStateTopic() + (new Ha::PollinSwitch{"Train", "11111", 4, publish})->withArea("Playroom")->withStateTopic(), + (new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }, publish})->withArea("Basement")->withStateTopic(), + (new Ha::EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }, publish})->withArea("Basement")->withStateTopic() }; - Ha::EasyHomeSwitch* otherSwitches[] = { - (Ha::Switch::Builder{new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }, publish}}) - .withArea("Basement")->withStateTopic()->build(), - (Ha::Switch::Builder{new Ha::EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }, publish}}) - .withArea("Basement")->withStateTopic()->build() - }; - - unordered_map mapSwitches; - unordered_multimap onSwitches; - unordered_multimap offSwitches; - void publishComponentConfig(Ha::Component& component) { StaticJsonDocument jsonDoc; component.buildConfig(jsonDoc); @@ -83,14 +74,7 @@ namespace Mqtt { publishComponentConfig(*cmp); } for (Ha::Switch* cmp : switches) { - mapSwitches.insert({string(cmp->id), cmp}); - publishComponentConfig(*cmp); - } - for (Ha::EasyHomeSwitch* cmp : otherSwitches) { - for (int i = 0; i < 8; i++) { - onSwitches.insert({cmp->on[i], cmp}); - offSwitches.insert({cmp->off[i], cmp}); - } + cmp->addToMap(); publishComponentConfig(*cmp); } ts.deleteTask(tPublishInit); @@ -123,12 +107,6 @@ namespace Mqtt { return; } } - for (Ha::Switch* cmd : otherSwitches) { - if (String{ cmd->commandTopic }.equals(topic)) { - cmd->onCommand(msg); - return; - } - } } void setup() { From 071e363c14a565770545825f2e15e8d15aa694b7 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Thu, 2 May 2024 12:44:30 +0200 Subject: [PATCH 14/61] make publisher accesible to entire Ha namespace --- gateway/include/ha.h | 15 +++++++-------- gateway/include/mqtt.h | 13 +++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/gateway/include/ha.h b/gateway/include/ha.h index 62fb34d..f5b726e 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -7,6 +7,7 @@ #define DEVICE_ID "rc-gateway" namespace Ha { + uint16_t (*publisher)(const char* topic, const char* message); struct Component { const char* name; @@ -70,18 +71,16 @@ namespace Ha { }; - typedef uint16_t (*publisherFunc)(const char* topic, const char* message); struct Switch : Command { static constexpr const char* type = "switch"; char stateTopic[TOPIC_SIZE]; const char* area; - publisherFunc publisher; virtual void onCommand(const char* msg){} virtual void addToMap(){} - Switch(const char* name, const char* id, publisherFunc publisher = nullptr) - : Command(name, id, type), publisher(publisher) { + Switch(const char* name, const char* id) + : Command(name, id, type) { sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); } @@ -121,8 +120,8 @@ namespace Ha { const char* group; unsigned char channel; - PollinSwitch(const char* name, const char* group, const unsigned char channel, publisherFunc publisher) - : Switch(name, Protocol_1::buildId(group, channel), publisher), group(group), channel(channel) { + PollinSwitch(const char* name, const char* group, const unsigned char channel) + : Switch(name, Protocol_1::buildId(group, channel)), group(group), channel(channel) { sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); } @@ -147,8 +146,8 @@ namespace Ha { unsigned long on[8] = {4326554, 4537114, 4767530, 4972714}; unsigned long off[8] = {4483146, 4626810, 4661562, 4819642}; - EasyHomeSwitch(const char* name, const char* id, unsigned long on[4], unsigned long off[4], publisherFunc publisher) - : Switch(name, id, publisher) { + EasyHomeSwitch(const char* name, const char* id, unsigned long on[4], unsigned long off[4]) + : Switch(name, id) { memcpy(&this->on[4], on, 4*sizeof(unsigned long)); memcpy(&this->off[4], off, 4*sizeof(unsigned long)); sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h index ccb4d84..1652ed6 100644 --- a/gateway/include/mqtt.h +++ b/gateway/include/mqtt.h @@ -48,12 +48,12 @@ namespace Mqtt { }; Ha::Switch* switches[] = { - (new Ha::PollinSwitch{"Meeting sensor", "00001", 1, publish})->withArea("Dining room")->withStateTopic(), - (new Ha::PollinSwitch{"Fire Tv", "00001", 2, publish})->withArea("Living room")->withStateTopic(), - (new Ha::PollinSwitch{"Diningroom player", "00001", 3, publish})->withArea("Dining room")->withStateTopic(), - (new Ha::PollinSwitch{"Train", "11111", 4, publish})->withArea("Playroom")->withStateTopic(), - (new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }, publish})->withArea("Basement")->withStateTopic(), - (new Ha::EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }, publish})->withArea("Basement")->withStateTopic() + (new Ha::PollinSwitch{"Meeting sensor", "00001", 1})->withArea("Dining room")->withStateTopic(), + (new Ha::PollinSwitch{"Fire Tv", "00001", 2})->withArea("Living room")->withStateTopic(), + (new Ha::PollinSwitch{"Diningroom player", "00001", 3})->withArea("Dining room")->withStateTopic(), + (new Ha::PollinSwitch{"Train", "11111", 4})->withArea("Playroom")->withStateTopic(), + (new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }})->withArea("Basement")->withStateTopic(), + (new Ha::EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }})->withArea("Basement")->withStateTopic() }; void publishComponentConfig(Ha::Component& component) { @@ -70,6 +70,7 @@ namespace Mqtt { // for (Ha::Component* cmp : sensors) { // publishComponentConfig(*cmp); // } + Ha::publisher = publish; for (Ha::Component* cmp : buttons) { publishComponentConfig(*cmp); } From 551c1300a623641336d58f7365a98e6c7c027c5f Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Thu, 2 May 2024 13:27:05 +0200 Subject: [PATCH 15/61] publish cleanup config on OTA updates --- gateway/include/ha.h | 4 ++++ gateway/include/mqtt.h | 6 ++++++ gateway/include/ota.h | 1 + gateway/include/utils.h | 20 ++++++++++++++++++++ 4 files changed, 31 insertions(+) create mode 100644 gateway/include/utils.h diff --git a/gateway/include/ha.h b/gateway/include/ha.h index f5b726e..500c12f 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -1,6 +1,7 @@ #pragma once #include +#include "utils.h" #define JSON_SIZE 512 #define TOPIC_SIZE 255 @@ -14,9 +15,11 @@ namespace Ha { char* id; const char* type; char configTopic[TOPIC_SIZE]; + static List configs; Component(const char* name, const char* id, const char* type) : name(name), id((char*)id), type(type) { sprintf(configTopic, "homeassistant/%s/rc-gateway/%s/config", type, id); + configs.add(this); } virtual void buildUniqueId(char* uniqueId) = 0; @@ -42,6 +45,7 @@ namespace Ha { identifiers.add(DEVICE_ID); } }; + List Component::configs; struct Command : Component { char commandTopic[TOPIC_SIZE]; diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h index 1652ed6..b97d3ab 100644 --- a/gateway/include/mqtt.h +++ b/gateway/include/mqtt.h @@ -81,6 +81,12 @@ namespace Mqtt { ts.deleteTask(tPublishInit); } + void publishCleanupConfig() { + for (List::Container* c = Ha::Component::configs.first; c; c = c->next) { + publish(c->t->configTopic, ""); + } + } + void publishBme280() { // StaticJsonDocument<255> jsonDoc; // jsonDoc["temperature"] = Bme::data.temp; diff --git a/gateway/include/ota.h b/gateway/include/ota.h index eb7a37d..6ff608e 100644 --- a/gateway/include/ota.h +++ b/gateway/include/ota.h @@ -11,6 +11,7 @@ namespace Ota { void setup() { ArduinoOTA.onStart([]() { Serial.println("Starting OTA"); + Mqtt::publishCleanupConfig(); Mqtt::disconnect(); }); ArduinoOTA.onEnd([]() { diff --git a/gateway/include/utils.h b/gateway/include/utils.h new file mode 100644 index 0000000..0c0bbe5 --- /dev/null +++ b/gateway/include/utils.h @@ -0,0 +1,20 @@ +#pragma once + +template +struct List { + struct Container { + T* t; + Container* next; + Container(T* t) : t(t) {} + }; + + Container* first; + Container* last; + + void add(T* t) { + Container* c = new Container{t}; + first == nullptr ? first = c : last->next = c; + last = c; + } + +}; From eace33902bb17d6f19916c049148e21c754cd14f Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Thu, 2 May 2024 19:40:04 +0200 Subject: [PATCH 16/61] separate Ha generic components from gateway specific ones --- gateway/include/devices.h | 63 +++++++++++++++++++++++++++++++++++++++ gateway/include/ha.h | 58 ----------------------------------- gateway/include/mqtt.h | 14 ++++----- 3 files changed, 70 insertions(+), 65 deletions(-) create mode 100644 gateway/include/devices.h diff --git a/gateway/include/devices.h b/gateway/include/devices.h new file mode 100644 index 0000000..eeecfcf --- /dev/null +++ b/gateway/include/devices.h @@ -0,0 +1,63 @@ +#pragma once + +#include "ha.h" + +using namespace Ha; + +struct PollinSwitch : Switch { + const char* group; + unsigned char channel; + + PollinSwitch(const char* name, const char* group, const unsigned char channel) + : Switch(name, Protocol_1::buildId(group, channel)), group(group), channel(channel) { + sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); + } + + void onCommand(const char* msg) override { + (String{ "ON" }.equals(msg)) ? mySwitch.switchOn(group, channel) : mySwitch.switchOff(group, channel); + publisher(stateTopic, msg); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Switch::buildConfig(jsonDoc); + JsonObject device = jsonDoc["device"]; + device["manufacturer"] = "Pollin"; + } + + void addToMap() override { + p1Switches.insert({ string(id), this }); + } + +}; + +struct EasyHomeSwitch : Switch { + unsigned long on[8] = { 4326554, 4537114, 4767530, 4972714 }; + unsigned long off[8] = { 4483146, 4626810, 4661562, 4819642 }; + + EasyHomeSwitch(const char* name, const char* id, unsigned long on[4], unsigned long off[4]) + : Switch(name, id) { + memcpy(&this->on[4], on, 4 * sizeof(unsigned long)); + memcpy(&this->off[4], off, 4 * sizeof(unsigned long)); + sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); + } + + void onCommand(const char* msg) override { + mySwitch.setProtocol(4); + String{ "ON" }.equals(msg) ? mySwitch.send(on[4], 24) : mySwitch.send(off[4], 24); + publisher(stateTopic, msg); + } + + void buildConfig(JsonDocument& jsonDoc) override { + Switch::buildConfig(jsonDoc); + JsonObject device = jsonDoc["device"]; + device["manufacturer"] = "Intertek"; + device["model"] = "Easy Home"; + } + + void addToMap() override { + for (int i = 0; i < 8; i++) { + onSwitches.insert({ on[i], this }); + offSwitches.insert({ off[i], this }); + } + } +}; diff --git a/gateway/include/ha.h b/gateway/include/ha.h index 500c12f..959d790 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -120,64 +120,6 @@ namespace Ha { } }; - struct PollinSwitch : Switch { - const char* group; - unsigned char channel; - - PollinSwitch(const char* name, const char* group, const unsigned char channel) - : Switch(name, Protocol_1::buildId(group, channel)), group(group), channel(channel) { - sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); - } - - void onCommand(const char* msg) override { - (String{ "ON" }.equals(msg)) ? mySwitch.switchOn(group, channel) : mySwitch.switchOff(group, channel); - publisher(stateTopic, msg); - } - - void buildConfig(JsonDocument& jsonDoc) override { - Switch::buildConfig(jsonDoc); - JsonObject device = jsonDoc["device"]; - device["manufacturer"] = "Pollin"; - } - - void addToMap() override { - p1Switches.insert({string(id), this}); - } - - }; - - struct EasyHomeSwitch : Switch { - unsigned long on[8] = {4326554, 4537114, 4767530, 4972714}; - unsigned long off[8] = {4483146, 4626810, 4661562, 4819642}; - - EasyHomeSwitch(const char* name, const char* id, unsigned long on[4], unsigned long off[4]) - : Switch(name, id) { - memcpy(&this->on[4], on, 4*sizeof(unsigned long)); - memcpy(&this->off[4], off, 4*sizeof(unsigned long)); - sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); - } - - void onCommand(const char* msg) override { - mySwitch.setProtocol(4); - String{ "ON" }.equals(msg) ? mySwitch.send(on[4], 24) : mySwitch.send(off[4], 24); - publisher(stateTopic, msg); - } - - void buildConfig(JsonDocument& jsonDoc) override { - Switch::buildConfig(jsonDoc); - JsonObject device = jsonDoc["device"]; - device["manufacturer"] = "Intertek"; - device["model"] = "Easy Home"; - } - - void addToMap() override { - for (int i = 0; i < 8; i++) { - onSwitches.insert({on[i], this}); - offSwitches.insert({off[i], this}); - } - } - }; - struct Sensor : Component { const char* deviceClass; const char* unitMeasure; diff --git a/gateway/include/mqtt.h b/gateway/include/mqtt.h index b97d3ab..418c2f0 100644 --- a/gateway/include/mqtt.h +++ b/gateway/include/mqtt.h @@ -2,7 +2,7 @@ #include #include -#include "ha.h" +#include "devices.h" #define MQTT_HOST IPAddress(192, 168, 5, 11) @@ -48,12 +48,12 @@ namespace Mqtt { }; Ha::Switch* switches[] = { - (new Ha::PollinSwitch{"Meeting sensor", "00001", 1})->withArea("Dining room")->withStateTopic(), - (new Ha::PollinSwitch{"Fire Tv", "00001", 2})->withArea("Living room")->withStateTopic(), - (new Ha::PollinSwitch{"Diningroom player", "00001", 3})->withArea("Dining room")->withStateTopic(), - (new Ha::PollinSwitch{"Train", "11111", 4})->withArea("Playroom")->withStateTopic(), - (new Ha::EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }})->withArea("Basement")->withStateTopic(), - (new Ha::EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }})->withArea("Basement")->withStateTopic() + (new PollinSwitch{"Meeting sensor", "00001", 1})->withArea("Dining room")->withStateTopic(), + (new PollinSwitch{"Fire Tv", "00001", 2})->withArea("Living room")->withStateTopic(), + (new PollinSwitch{"Diningroom player", "00001", 3})->withArea("Dining room")->withStateTopic(), + (new PollinSwitch{"Train", "11111", 4})->withArea("Playroom")->withStateTopic(), + (new EasyHomeSwitch{"FritzBox", "easy_home_a", (unsigned long[4]) { 4483136, 4626800, 4661552, 4819632 }, (unsigned long[4]) { 4326544, 4537104, 4767520, 4972704 }})->withArea("Basement")->withStateTopic(), + (new EasyHomeSwitch{"Outside", "easy_home_b", (unsigned long[4]) { 4483140, 4626804, 4661556, 4819636 }, (unsigned long[4]) { 4326548, 4537108, 4767524, 4972708 }})->withArea("Basement")->withStateTopic() }; void publishComponentConfig(Ha::Component& component) { From e67ce3b33cffdcfacd325e9e6dfbe54a5551601d Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Thu, 2 May 2024 21:24:41 +0200 Subject: [PATCH 17/61] extract StateConfig into a dedicated class --- gateway/include/ha.h | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/gateway/include/ha.h b/gateway/include/ha.h index 959d790..54e41e0 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -75,9 +75,18 @@ namespace Ha { }; - struct Switch : Command { - static constexpr const char* type = "switch"; + template + struct StateConfig { char stateTopic[TOPIC_SIZE]; + + T* withStateTopic() { + sprintf(stateTopic, "homeassistant/%s/rc-gateway/%s/state", ((T*)this)->type, ((T*)this)->id); + return (T*)this; + } + }; + + struct Switch : Command, StateConfig { + static constexpr const char* type = "switch"; const char* area; virtual void onCommand(const char* msg){} @@ -105,11 +114,6 @@ namespace Ha { if (stateTopic[0]) jsonDoc["state_topic"] = stateTopic; } - Switch* withStateTopic() { - sprintf(stateTopic, "homeassistant/%s/rc-gateway/%s/state", type, id); - return this; - } - Switch* withArea(const char* area) { this->area = area; return this; From 86b2d75ea64548b8943ede7a501fe515987da364 Mon Sep 17 00:00:00 2001 From: Nicu Hodos Date: Thu, 2 May 2024 18:23:57 +0200 Subject: [PATCH 18/61] separate generic Ha components - could be used in a library --- gateway/README.md | 4 +- gateway/include/devices.h | 21 ++---- gateway/include/ha.h | 151 ++++++++++++++++++++++---------------- gateway/include/mqtt.h | 6 +- gateway/include/ota.h | 1 + 5 files changed, 100 insertions(+), 83 deletions(-) diff --git a/gateway/README.md b/gateway/README.md index 40206bc..be137bd 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -44,7 +44,7 @@ enum SensorId : unsigned short { }; ``` #### Stairs temperature -##### Value and volatage +##### Value and voltage ```json { "sensor": { @@ -66,7 +66,7 @@ enum SensorId : unsigned short { } ``` #### Oil sensor -##### Value and volatage +##### Value and voltage ```json { "sensor": { diff --git a/gateway/include/devices.h b/gateway/include/devices.h index eeecfcf..27f0983 100644 --- a/gateway/include/devices.h +++ b/gateway/include/devices.h @@ -1,16 +1,20 @@ #pragma once +#define MAIN_DEVICE_ID "rc-gateway" + #include "ha.h" using namespace Ha; +DeviceConfig* gatewayConfig = (new DeviceConfig{MAIN_DEVICE_ID, "RC Gateway"})->withManufacturer("Adafruit")->withModel("Huzzah Esp8266"); + struct PollinSwitch : Switch { const char* group; unsigned char channel; PollinSwitch(const char* name, const char* group, const unsigned char channel) : Switch(name, Protocol_1::buildId(group, channel)), group(group), channel(channel) { - sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); + asDevice((new DeviceConfig{id, name})->withManufacturer("Pollin")->withParent(gatewayConfig)); } void onCommand(const char* msg) override { @@ -18,12 +22,6 @@ struct PollinSwitch : Switch { publisher(stateTopic, msg); } - void buildConfig(JsonDocument& jsonDoc) override { - Switch::buildConfig(jsonDoc); - JsonObject device = jsonDoc["device"]; - device["manufacturer"] = "Pollin"; - } - void addToMap() override { p1Switches.insert({ string(id), this }); } @@ -38,7 +36,7 @@ struct EasyHomeSwitch : Switch { : Switch(name, id) { memcpy(&this->on[4], on, 4 * sizeof(unsigned long)); memcpy(&this->off[4], off, 4 * sizeof(unsigned long)); - sprintf(commandTopic, "homeassistant/%s/rc-gateway/%s/set", type, id); + asDevice((new DeviceConfig{id, name})->withManufacturer("Intertek")->withModel("Easy Home")->withParent(gatewayConfig)); } void onCommand(const char* msg) override { @@ -47,13 +45,6 @@ struct EasyHomeSwitch : Switch { publisher(stateTopic, msg); } - void buildConfig(JsonDocument& jsonDoc) override { - Switch::buildConfig(jsonDoc); - JsonObject device = jsonDoc["device"]; - device["manufacturer"] = "Intertek"; - device["model"] = "Easy Home"; - } - void addToMap() override { for (int i = 0; i < 8; i++) { onSwitches.insert({ on[i], this }); diff --git a/gateway/include/ha.h b/gateway/include/ha.h index 54e41e0..864119d 100644 --- a/gateway/include/ha.h +++ b/gateway/include/ha.h @@ -5,73 +5,123 @@ #define JSON_SIZE 512 #define TOPIC_SIZE 255 -#define DEVICE_ID "rc-gateway" namespace Ha { uint16_t (*publisher)(const char* topic, const char* message); + struct DeviceConfig { + const char* id; + const char* name; + const char* model; + const char* manufacturer; + const char* area; + DeviceConfig* parent = nullptr; + + DeviceConfig(const char* id, const char* name) : id(id), name(name) {} + + void buildConfig(JsonDocument& jsonDoc) { + JsonObject device = jsonDoc.createNestedObject("device"); + device["name"] = name; + // JsonArray connections = device.createNestedArray("connections"); + // JsonArray mac = connections.createNestedArray(); + // mac.add("mac"); + // mac.add(WiFi.macAddress()); + JsonArray identifiers = device.createNestedArray("identifiers"); + identifiers.add(id); + if (model) device["model"] = model; + if (manufacturer) device["manufacturer"] = manufacturer; + if (area) device["suggested_area"] = area; + if (parent) device["via_device"] = parent->id; + } + + DeviceConfig* withModel(const char* value) { + model = value; + return this; + } + + DeviceConfig* withManufacturer(const char* value) { + manufacturer = value; + return this; + } + + DeviceConfig* withArea(const char* value) { + area = value; + return this; + } + + DeviceConfig* withParent(DeviceConfig* deviceConfig) { + parent = deviceConfig; + return this; + } + }; + struct Component { const char* name; char* id; const char* type; char configTopic[TOPIC_SIZE]; - static List configs; + DeviceConfig* mainDevice; + static List components; Component(const char* name, const char* id, const char* type) : name(name), id((char*)id), type(type) { - sprintf(configTopic, "homeassistant/%s/rc-gateway/%s/config", type, id); - configs.add(this); + sprintf(configTopic, "homeassistant/%s/%s/%s/config", type, MAIN_DEVICE_ID, id); + components.add(this); } virtual void buildUniqueId(char* uniqueId) = 0; virtual void buildConfig(JsonDocument& jsonDoc) { - buildDeviceConfig(jsonDoc); + if (mainDevice) mainDevice->buildConfig(jsonDoc); jsonDoc["name"] = name; char uniqueId[50]; buildUniqueId(uniqueId); jsonDoc["unique_id"] = uniqueId; } + }; - virtual void buildDeviceConfig(JsonDocument& jsonDoc) { - JsonObject device = jsonDoc.createNestedObject("device"); - device["name"] = "RC Gateway"; - device["model"] = "Huzzah Esp8266"; - device["manufacturer"] = "Adafruit"; - JsonArray connections = device.createNestedArray("connections"); - JsonArray mac = connections.createNestedArray(); - mac.add("mac"); - mac.add(WiFi.macAddress()); - JsonArray identifiers = device.createNestedArray("identifiers"); - identifiers.add(DEVICE_ID); + template + struct EntityConfig : Component { + + EntityConfig(const char* name, const char* id, const char* type) : Component(name, id, type) { + } + + T* withArea(const char* value) { + if (mainDevice) mainDevice->withArea(value); + return static_cast(this); + } + + T* asDevice(DeviceConfig* deviceConfig) { + mainDevice = deviceConfig; + return static_cast(this); } }; - List Component::configs; + + List Component::components; - struct Command : Component { + template + struct Command : EntityConfig { char commandTopic[TOPIC_SIZE]; - Command(const char* name, const char* id, const char* type) : Component(name, id, type) { + Command(const char* name, const char* id, const char* type) : EntityConfig(name, id, type) { } void buildUniqueId(char* uniqueId) override { - sprintf(uniqueId, "rc_gateway_%s", id); + sprintf(uniqueId, "%s_%s", MAIN_DEVICE_ID, this->id); } void buildConfig(JsonDocument& jsonDoc) override { - Component::buildConfig(jsonDoc); + EntityConfig::buildConfig(jsonDoc); jsonDoc["command_topic"] = commandTopic; } }; typedef void (*onMessage)(const char* msg); - struct Button : Command { + struct Button : Command