diff --git a/platformio.ini b/platformio.ini index d2abb92..6069311 100644 --- a/platformio.ini +++ b/platformio.ini @@ -42,7 +42,8 @@ lib_deps = https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/tzapu/WiFiManager.git#v2.0.17 rblb/Nostrduino@1.2.8 - + elims/PsychicMqttClient@^0.2.0 + [env:lolin_s3_mini] extends = btclock_base board = lolin_s3_mini diff --git a/src/lib/config.cpp b/src/lib/config.cpp index d7f403f..8ebe561 100644 --- a/src/lib/config.cpp +++ b/src/lib/config.cpp @@ -100,6 +100,12 @@ void setup() setupMiningPoolStatsFetchTask(); } + if (preferences.getBool("mqttEnabled", DEFAULT_MQTT_ENABLED)) + { + if (setupMqtt()) + setupMqttTask(); + } + setupButtonTask(); setupOTA(); diff --git a/src/lib/config.hpp b/src/lib/config.hpp index 87dd4d6..0d705e9 100644 --- a/src/lib/config.hpp +++ b/src/lib/config.hpp @@ -30,6 +30,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 7ca89fb..50e634d 100644 --- a/src/lib/defaults.hpp +++ b/src/lib/defaults.hpp @@ -62,6 +62,10 @@ #define DEFAULT_MINING_POOL_NAME "ocean" #define DEFAULT_MINING_POOL_USER "38Qkkei3SuF1Eo45BaYmRHUneRD54yyTFy" // Random actual Ocean hasher +#define DEFAULT_MQTT_ENABLED false +#define DEFAULT_MQTT_URL "" +#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..b2a7b09 --- /dev/null +++ b/src/lib/mqtt.cpp @@ -0,0 +1,131 @@ +#include "mqtt.hpp" + +TaskHandle_t mqttTaskHandle = NULL; + +// WiFiClient wifiClient; +//PubSubClient client(wifiClient); +PsychicMqttClient mqttClient; + +// 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) +{ + 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() +// { +// const String willTopic = getDeviceTopic() + "status"; +// mqttClient.connect() +// 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; +// } + +boolean setupMqtt() +{ + const String url = preferences.getString("mqttUrl", DEFAULT_MQTT_URL); + if (url == "") + { + Serial.println("[MQTT] url not set"); + return false; + } + Serial.print("[MQTT] url: "); + Serial.println(url.c_str()); + + mqttClient.setClientId(getMyHostname().c_str()); + mqttClient.setServer(url.c_str()); + // if (url.startsWith("mqtts:") || url.startsWith("wss:")) + // mqttClient.attachArduinoCACertBundle(); + mqttClient.connect(); + + const String willTopic = getDeviceTopic() + "status"; + mqttClient.setWill(willTopic.c_str(), 0, 1, "offline"); + publish("status", "online", true); + + return true; +} + +void mqttTask(void *pvParameters) +{ + int t=0; + while (1) + { + // client.loop(); + vTaskDelay(pdMS_TO_TICKS(1000)); + if (t++ % 10 == 0) + { +#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(); + 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()); + 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 publishForDevice(const char *topic, const char *payload) +{ + publishForDevice(topic, payload, false); +} + +void publishForDevice(const char *topic, const char *payload, boolean retain) +{ + const String fullTopic = getDeviceTopic() + topic; + publish(fullTopic.c_str(), payload, retain); +} + +void publish(const char *topic, const char *payload) +{ + publish(topic, payload, false); +} + +void publish(const char *topic, const char *payload, boolean retain) +{ + if (!mqttClient.publish(topic, 0, retain, payload)) + { + 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..e1956c8 --- /dev/null +++ b/src/lib/mqtt.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include +#include "lib/shared.hpp" + +boolean setupMqtt(); +void setupMqttTask(); +void publishForDevice(const char *topic, const char *payload); +void publishForDevice(const char *topic, const char *payload, boolean retain); +void publish(const char *topic, const char *payload); +void publish(const char *topic, const char *payload, boolean retain); diff --git a/src/lib/webserver.cpp b/src/lib/webserver.cpp index 8b031d6..32b18bf 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", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl"}; + String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", + "nostrRelay", "bitaxeHostname", "nostrZapPubkey", + "httpAuthUser", "httpAuthPass", "gitReleaseUrl", + "mqttUrl", "mqttRootTopic"}; for (String setting : strSettings) { @@ -549,7 +552,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json) "mowMode", "suffixShareDot", "flOffWhenDark", "flAlwaysOn", "flDisable", "flFlashOnUpd", "mempoolSecure", "useNostr", "bitaxeEnabled", - "miningPoolStats", "verticalDesc", + "miningPoolStats", "verticalDesc", "mqttEnabled", "nostrZapNotify", "stagingSource", "httpAuthEnabled"}; for (String setting : boolSettings) @@ -718,6 +721,11 @@ void onApiSettingsGet(AsyncWebServerRequest *request) root["miningPoolName"] = preferences.getString("miningPoolName", DEFAULT_MINING_POOL_NAME); root["miningPoolUser"] = preferences.getString("miningPoolUser", DEFAULT_MINING_POOL_USER); root["availablePools"] = PoolFactory::getAvailablePools(); + + root["mqttEnabled"] = preferences.getBool("mqttEnabled", DEFAULT_MQTT_ENABLED); + root["mqttUrl"] = preferences.getString("mqttUrl", DEFAULT_MQTT_URL); + 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);