From 67f1d03919dec4c2f41ba0e20ba9bdca6098a21d Mon Sep 17 00:00:00 2001 From: Sander Date: Fri, 29 Nov 2024 14:48:16 +0100 Subject: [PATCH 1/3] add mqtt module --- platformio.ini | 1 + src/lib/config.cpp | 6 +++ src/lib/config.hpp | 1 + src/lib/defaults.hpp | 3 ++ src/lib/mqtt.cpp | 112 ++++++++++++++++++++++++++++++++++++++++++ src/lib/mqtt.hpp | 9 ++++ src/lib/webserver.cpp | 11 ++++- 7 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 src/lib/mqtt.cpp create mode 100644 src/lib/mqtt.hpp diff --git a/platformio.ini b/platformio.ini index 7b0d229..fe5bdf6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -42,6 +42,7 @@ lib_deps = https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 rblb/Nostrduino@1.2.8 + knolleary/PubSubClient@2.8 [env:lolin_s3_mini] extends = btclock_base diff --git a/src/lib/config.cpp b/src/lib/config.cpp index 8f954b2..9e4d5a3 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -92,6 +92,12 @@ void setup() setupBitaxeFetchTask(); } + if (preferences.getBool("mqttEnabled", DEFAULT_MQTT_ENABLED)) + { + setupMqtt(); + setupMqttTask(); + } + setupButtonTask(); setupOTA(); diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 8c9da90..21f16c5 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -29,6 +29,7 @@ #include "PCA9685.h" #include "BH1750.h" #endif +#include "lib/mqtt.hpp" #define NTP_SERVER "pool.ntp.org" #define DEFAULT_TIME_OFFSET_SECONDS 3600 diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 8af3b5b..3a06218 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -56,6 +56,9 @@ #define DEFAULT_BITAXE_ENABLED false #define DEFAULT_BITAXE_HOSTNAME "bitaxe1" +#define DEFAULT_MQTT_ENABLED false +#define DEFAULT_MQTT_ROOTTOPIC "home/" + #define DEFAULT_ZAP_NOTIFY_ENABLED false #define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422" #define DEFAULT_LED_FLASH_ON_ZAP true diff --git a/src/lib/mqtt.cpp b/src/lib/mqtt.cpp new file mode 100644 index 0000000..185a7f5 --- /dev/null +++ b/src/lib/mqtt.cpp @@ -0,0 +1,112 @@ +#include "mqtt.hpp" + +TaskHandle_t mqttTaskHandle = NULL; + +WiFiClient wifiClient; +PubSubClient client(wifiClient); + +void onMqttCallback(char* topic, byte* payload, unsigned int length) +{ + Serial.println("MQTT message arrived"); +} + +boolean connectMqtt() +{ + boolean result = client.connect("btclockClient"); + if (!result) + { + Serial.println("[MQTT] could not connect"); + } + return result; +} + +void setupMqtt() +{ + const String host = preferences.getString("mqttHost", ""); + if (host == "") + { + Serial.println("[MQTT] host not set"); + return; + } + Serial.print("[MQTT] host: "); + Serial.println(host.c_str()); + + IPAddress addr((uint32_t)0); + if(!WiFi.hostByName(host.c_str(), addr)) + { + Serial.println("[MQTT] host lookup fail"); + return; + } + client.setServer(addr, 1883); + client.setCallback(onMqttCallback); + + connectMqtt(); +} + +// avoid circular deps, just forward declare externs used here. +bool hasLightLevel(); +float getLightLevel(); +String getMyHostname(); + +void mqttTask(void *pvParameters) +{ + int t=0; + while (1) + { + client.loop(); + vTaskDelay(pdMS_TO_TICKS(1000)); + if (t++ % 10 == 0) + { + if (!client.connected()) + { + // reconnect + if (!connectMqtt()) + return; + } + + if (hasLightLevel()) + { + std::string lux_s = std::to_string(static_cast(std::round(getLightLevel()))); + publish("sensors/lux", lux_s.c_str()); + } + if (WiFi.isConnected()) + { + int8_t rssi = WiFi.RSSI(); + std::string rssi_s = std::to_string(static_cast(rssi)); + publish("wifi/rssi", rssi_s.c_str()); + } + std::string heap_free_s = std::to_string(static_cast(ESP.getFreeHeap())); + publish("mem/heap_free", heap_free_s.c_str()); + std::string heap_size_s = std::to_string(static_cast(ESP.getHeapSize())); + publish("mem/heap_size", heap_size_s.c_str()); + } + } +} + +void setupMqttTask() +{ + xTaskCreate(mqttTask, "mqttTask", 8192, NULL, 10, &mqttTaskHandle); +} + +void publish(const char *topic, const char *value) +{ + if (!client.connected()) + { + Serial.println("[MQTT] not connected"); + return; + } + + const String hostname = getMyHostname(); + const String rootTopic = preferences.getString("mqttRootTopic", DEFAULT_MQTT_ROOTTOPIC); + String fullTopic = rootTopic; + if (!rootTopic.endsWith("/") && rootTopic != "") + { + fullTopic += "/"; + } + fullTopic += hostname + "/" + topic; + + if (!client.publish(fullTopic.c_str(), value)) + { + Serial.println("[MQTT] could not write"); + } +} \ No newline at end of file diff --git a/src/lib/mqtt.hpp b/src/lib/mqtt.hpp new file mode 100644 index 0000000..229d54b --- /dev/null +++ b/src/lib/mqtt.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include "lib/shared.hpp" +#include "PubSubClient.h" + +void setupMqtt(); +void setupMqttTask(); +void publish(const char *key, const char *value); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 777fe0b..729292d 100644 --- a/src/lib/webserver.cpp +++ b/src/lib/webserver.cpp @@ -510,7 +510,10 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) settings["timePerScreen"].as() * 60); } - String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl"}; + String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", + "nostrRelay", "bitaxeHostname", "nostrZapPubkey", + "httpAuthUser", "httpAuthPass", "gitReleaseUrl", + "mqttHost", "mqttRootTopic"}; for (String setting : strSettings) { @@ -546,7 +549,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) "mdnsEnabled", "otaEnabled", "stealFocus", "mcapBigChar", "useSatsSymbol", "useBlkCountdown", "suffixPrice", "disableLeds", "ownDataSource", - "mowMode", "suffixShareDot", + "mowMode", "suffixShareDot", "mqttEnabled", "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; @@ -711,6 +714,10 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["bitaxeEnabled"] = preferences.getBool("bitaxeEnabled", DEFAULT_BITAXE_ENABLED); root["bitaxeHostname"] = preferences.getString("bitaxeHostname", DEFAULT_BITAXE_HOSTNAME); + root["mqttEnabled"] = preferences.getBool("mqttEnabled", DEFAULT_MQTT_ENABLED); + root["mqttHost"] = preferences.getString("mqttHost", ""); + root["mqttRootTopic"] = preferences.getString("mqttRootTopic", DEFAULT_MQTT_ROOTTOPIC); + root["httpAuthEnabled"] = preferences.getBool("httpAuthEnabled", DEFAULT_HTTP_AUTH_ENABLED); root["httpAuthUser"] = preferences.getString("httpAuthUser", DEFAULT_HTTP_AUTH_USERNAME); root["httpAuthPass"] = preferences.getString("httpAuthPass", DEFAULT_HTTP_AUTH_PASSWORD); From adf6b38bfbcae8208f0d66e6fdea7a9c47d08e5a Mon Sep 17 00:00:00 2001 From: Sander Date: Wed, 4 Dec 2024 11:32:08 +0100 Subject: [PATCH 2/3] add will message on /status topic, so status is always available even when offline. add wifi/bssid topic to track which AP is connected fix reconnect breaking out of loop --- src/lib/mqtt.cpp | 47 +++++++++++++++++++++++++++++++---------------- src/lib/mqtt.hpp | 3 ++- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/lib/mqtt.cpp b/src/lib/mqtt.cpp index 185a7f5..1fc9d1e 100644 --- a/src/lib/mqtt.cpp +++ b/src/lib/mqtt.cpp @@ -5,18 +5,39 @@ TaskHandle_t mqttTaskHandle = NULL; WiFiClient wifiClient; PubSubClient client(wifiClient); +// avoid circular deps, just forward declare externs used here. +bool hasLightLevel(); +float getLightLevel(); +String getMyHostname(); + void onMqttCallback(char* topic, byte* payload, unsigned int length) { Serial.println("MQTT message arrived"); } +const String getDeviceTopic() +{ + const String hostname = getMyHostname(); + const String rootTopic = preferences.getString("mqttRootTopic", DEFAULT_MQTT_ROOTTOPIC); + String fullTopic = rootTopic; + if (!rootTopic.endsWith("/") && rootTopic != "") + { + fullTopic += "/"; + } + fullTopic += hostname + "/"; + return String(fullTopic); +} + boolean connectMqtt() { - boolean result = client.connect("btclockClient"); + const String willTopic = getDeviceTopic() + "status"; + boolean result = client.connect("btclockClient", willTopic.c_str(), 0, true, "offline"); if (!result) { Serial.println("[MQTT] could not connect"); + return result; } + publish("status", "online", true); return result; } @@ -43,11 +64,6 @@ void setupMqtt() connectMqtt(); } -// avoid circular deps, just forward declare externs used here. -bool hasLightLevel(); -float getLightLevel(); -String getMyHostname(); - void mqttTask(void *pvParameters) { int t=0; @@ -61,7 +77,7 @@ void mqttTask(void *pvParameters) { // reconnect if (!connectMqtt()) - return; + continue; } if (hasLightLevel()) @@ -74,6 +90,7 @@ void mqttTask(void *pvParameters) int8_t rssi = WiFi.RSSI(); std::string rssi_s = std::to_string(static_cast(rssi)); publish("wifi/rssi", rssi_s.c_str()); + publish("wifi/bssid", WiFi.BSSIDstr().c_str()); } std::string heap_free_s = std::to_string(static_cast(ESP.getFreeHeap())); publish("mem/heap_free", heap_free_s.c_str()); @@ -89,6 +106,11 @@ void setupMqttTask() } void publish(const char *topic, const char *value) +{ + publish(topic, value, false); +} + +void publish(const char *topic, const char *value, boolean retain) { if (!client.connected()) { @@ -96,16 +118,9 @@ void publish(const char *topic, const char *value) return; } - const String hostname = getMyHostname(); - const String rootTopic = preferences.getString("mqttRootTopic", DEFAULT_MQTT_ROOTTOPIC); - String fullTopic = rootTopic; - if (!rootTopic.endsWith("/") && rootTopic != "") - { - fullTopic += "/"; - } - fullTopic += hostname + "/" + topic; + const String fullTopic = getDeviceTopic() + topic; - if (!client.publish(fullTopic.c_str(), value)) + if (!client.publish(fullTopic.c_str(), value, retain)) { Serial.println("[MQTT] could not write"); } diff --git a/src/lib/mqtt.hpp b/src/lib/mqtt.hpp index 229d54b..d00d4ad 100644 --- a/src/lib/mqtt.hpp +++ b/src/lib/mqtt.hpp @@ -6,4 +6,5 @@ void setupMqtt(); void setupMqttTask(); -void publish(const char *key, const char *value); +void publish(const char *topic, const char *value); +void publish(const char *topic, const char *value, boolean retain); From ee81edbcd2f2ef9638fec6d6224405be4dc7fdb8 Mon Sep 17 00:00:00 2001 From: Sander Date: Fri, 6 Dec 2024 11:08:55 +0100 Subject: [PATCH 3/3] exclude light sensor calls on unsupported devices add mqtt default host define --- src/lib/defaults.hpp | 1 + src/lib/mqtt.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/defaults.hpp b/src/lib/defaults.hpp index 3a06218..fa6603f 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -57,6 +57,7 @@ #define DEFAULT_BITAXE_HOSTNAME "bitaxe1" #define DEFAULT_MQTT_ENABLED false +#define DEFAULT_MQTT_HOST "" #define DEFAULT_MQTT_ROOTTOPIC "home/" #define DEFAULT_ZAP_NOTIFY_ENABLED false diff --git a/src/lib/mqtt.cpp b/src/lib/mqtt.cpp index 1fc9d1e..b522bd9 100644 --- a/src/lib/mqtt.cpp +++ b/src/lib/mqtt.cpp @@ -6,8 +6,10 @@ WiFiClient wifiClient; PubSubClient client(wifiClient); // avoid circular deps, just forward declare externs used here. +#ifdef HAS_FRONTLIGHT bool hasLightLevel(); float getLightLevel(); +#endif String getMyHostname(); void onMqttCallback(char* topic, byte* payload, unsigned int length) @@ -43,7 +45,7 @@ boolean connectMqtt() void setupMqtt() { - const String host = preferences.getString("mqttHost", ""); + const String host = preferences.getString("mqttHost", DEFAULT_MQTT_HOST); if (host == "") { Serial.println("[MQTT] host not set"); @@ -80,11 +82,13 @@ void mqttTask(void *pvParameters) continue; } +#ifdef HAS_FRONTLIGHT if (hasLightLevel()) { std::string lux_s = std::to_string(static_cast(std::round(getLightLevel()))); publish("sensors/lux", lux_s.c_str()); } +#endif if (WiFi.isConnected()) { int8_t rssi = WiFi.RSSI();