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);