From 8b2088103a74e616ca82fe043de55cb7ed58e329 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Mon, 29 May 2023 05:44:59 +0300 Subject: platformio: split code into libraries --- platformio/common/libs/led/homekit/led.cpp | 9 +- platformio/common/libs/led/homekit/led.h | 13 +- platformio/common/libs/led/library.json | 2 +- platformio/common/libs/main/homekit/main.cpp | 194 +++++++++++++ platformio/common/libs/main/homekit/main.h | 48 ++++ platformio/common/libs/main/library.json | 12 + platformio/common/libs/mqtt/homekit/mqtt.cpp | 18 -- platformio/common/libs/mqtt/homekit/mqtt.h | 18 -- .../common/libs/mqtt/homekit/mqtt/module.cpp | 26 ++ platformio/common/libs/mqtt/homekit/mqtt/module.h | 47 +++ platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp | 164 +++++++++++ platformio/common/libs/mqtt/homekit/mqtt/mqtt.h | 48 ++++ platformio/common/libs/mqtt/homekit/mqtt/payload.h | 15 + platformio/common/libs/mqtt/library.json | 3 +- .../homekit/mqtt/module/diagnostics.cpp | 47 +++ .../homekit/mqtt/module/diagnostics.h | 46 +++ .../libs/mqtt_module_diagnostics/library.json | 7 + .../mqtt_module_ota/homekit/mqtt/module/ota.cpp | 162 +++++++++++ .../libs/mqtt_module_ota/homekit/mqtt/module/ota.h | 72 +++++ .../common/libs/mqtt_module_ota/library.json | 10 + .../homekit/mqtt/module/temphum.cpp | 23 ++ .../homekit/mqtt/module/temphum.h | 28 ++ .../common/libs/mqtt_module_temphum/library.json | 10 + platformio/common/libs/temphum/homekit/temphum.cpp | 87 ++++++ platformio/common/libs/temphum/homekit/temphum.h | 38 +++ platformio/common/libs/temphum/library.json | 8 + platformio/dumb_mqtt/.gitignore | 3 + platformio/dumb_mqtt/src/main.cpp | 12 + platformio/temphum/src/leds.cpp | 11 - platformio/temphum/src/leds.h | 15 - platformio/temphum/src/main.cpp | 217 ++------------ platformio/temphum/src/mqtt.cpp | 316 --------------------- platformio/temphum/src/mqtt.h | 114 -------- platformio/temphum/src/temphum.cpp | 76 ----- platformio/temphum/src/temphum.h | 37 --- 35 files changed, 1145 insertions(+), 811 deletions(-) create mode 100644 platformio/common/libs/main/homekit/main.cpp create mode 100644 platformio/common/libs/main/homekit/main.h create mode 100644 platformio/common/libs/main/library.json delete mode 100644 platformio/common/libs/mqtt/homekit/mqtt.cpp delete mode 100644 platformio/common/libs/mqtt/homekit/mqtt.h create mode 100644 platformio/common/libs/mqtt/homekit/mqtt/module.cpp create mode 100644 platformio/common/libs/mqtt/homekit/mqtt/module.h create mode 100644 platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp create mode 100644 platformio/common/libs/mqtt/homekit/mqtt/mqtt.h create mode 100644 platformio/common/libs/mqtt/homekit/mqtt/payload.h create mode 100644 platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.cpp create mode 100644 platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.h create mode 100644 platformio/common/libs/mqtt_module_diagnostics/library.json create mode 100644 platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.cpp create mode 100644 platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.h create mode 100644 platformio/common/libs/mqtt_module_ota/library.json create mode 100644 platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.cpp create mode 100644 platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.h create mode 100644 platformio/common/libs/mqtt_module_temphum/library.json create mode 100644 platformio/common/libs/temphum/homekit/temphum.cpp create mode 100644 platformio/common/libs/temphum/homekit/temphum.h create mode 100644 platformio/common/libs/temphum/library.json create mode 100644 platformio/dumb_mqtt/.gitignore create mode 100644 platformio/dumb_mqtt/src/main.cpp delete mode 100644 platformio/temphum/src/leds.cpp delete mode 100644 platformio/temphum/src/leds.h delete mode 100644 platformio/temphum/src/mqtt.cpp delete mode 100644 platformio/temphum/src/mqtt.h delete mode 100644 platformio/temphum/src/temphum.cpp delete mode 100644 platformio/temphum/src/temphum.h (limited to 'platformio') diff --git a/platformio/common/libs/led/homekit/led.cpp b/platformio/common/libs/led/homekit/led.cpp index acc2140..ffefb04 100644 --- a/platformio/common/libs/led/homekit/led.cpp +++ b/platformio/common/libs/led/homekit/led.cpp @@ -1,6 +1,6 @@ #include "led.h" -namespace homekit { +namespace homekit::led { void Led::on_off(uint16_t delay_ms, bool last_delay) const { on(); @@ -17,4 +17,11 @@ void Led::blink(uint8_t count, uint16_t delay_ms) const { } } + +#ifdef CONFIG_TARGET_NODEMCU +const Led* board_led = new Led(CONFIG_BOARD_LED_GPIO); +#endif +const Led* mcu_led = new Led(CONFIG_MCU_LED_GPIO); + + } diff --git a/platformio/common/libs/led/homekit/led.h b/platformio/common/libs/led/homekit/led.h index 513bfea..775d2eb 100644 --- a/platformio/common/libs/led/homekit/led.h +++ b/platformio/common/libs/led/homekit/led.h @@ -1,10 +1,10 @@ -#ifndef COMMON_HOMEKIT_LED_H -#define COMMON_HOMEKIT_LED_H +#ifndef HOMEKIT_LIB_LED_H +#define HOMEKIT_LIB_LED_H #include #include -namespace homekit { +namespace homekit::led { class Led { private: @@ -23,6 +23,11 @@ public: void blink(uint8_t count, uint16_t delay_ms) const; }; +#ifdef CONFIG_TARGET_NODEMCU +extern const Led* board_led; +#endif +extern const Led* mcu_led; + } -#endif //COMMON_HOMEKIT_LED_H +#endif //HOMEKIT_LIB_LED_H diff --git a/platformio/common/libs/led/library.json b/platformio/common/libs/led/library.json index 69a3ac3..6785d42 100644 --- a/platformio/common/libs/led/library.json +++ b/platformio/common/libs/led/library.json @@ -1,6 +1,6 @@ { "name": "homekit_led", - "version": "1.0.6", + "version": "1.0.8", "build": { "flags": "-I../../include" } diff --git a/platformio/common/libs/main/homekit/main.cpp b/platformio/common/libs/main/homekit/main.cpp new file mode 100644 index 0000000..fd08925 --- /dev/null +++ b/platformio/common/libs/main/homekit/main.cpp @@ -0,0 +1,194 @@ +#include "./main.h" +#include +#include +#include +#include + +namespace homekit::main { + +enum WorkingMode working_mode = WorkingMode::NORMAL; +static const uint16_t recovery_boot_detection_ms = 2000; +static const uint8_t recovery_boot_delay_ms = 100; + +static volatile enum WiFiConnectionState wifi_state = WiFiConnectionState::WAITING; +static void* service = nullptr; +static WiFiEventHandler wifiConnectHandler, wifiDisconnectHandler; +static Ticker wifiTimer; +static mqtt::MqttDiagnosticsModule* mqttDiagModule; +static mqtt::MqttOtaModule* mqttOtaModule; + +#if MQTT_BLINK +static StopWatch blinkStopWatch; +#endif + +#ifndef CONFIG_TARGET_ESP01 +static DNSServer* dnsServer = nullptr; +#endif + +static void onWifiConnected(const WiFiEventStationModeGotIP& event); +static void onWifiDisconnected(const WiFiEventStationModeDisconnected& event); + +static void wifiConnect() { + const char *ssid, *psk, *hostname; + auto cfg = config::read(); + wifi::getConfig(cfg, &ssid, &psk, &hostname); + + PRINTF("Wi-Fi STA creds: ssid=%s, psk=%s, hostname=%s\n", ssid, psk, hostname); + + wifi_state = WiFiConnectionState::WAITING; + + WiFi.mode(WIFI_STA); + WiFi.hostname(hostname); + WiFi.begin(ssid, psk); + + PRINT("connecting to wifi.."); +} + +#ifndef CONFIG_TARGET_ESP01 +static void wifiHotspot() { + led::mcu_led->on(); + + auto scanResults = wifi::scan(); + + WiFi.mode(WIFI_AP); + WiFi.softAP(wifi::AP_SSID); + + dnsServer = new DNSServer(); + dnsServer->start(53, "*", WiFi.softAPIP()); + + service = new HttpServer(scanResults); + ((HttpServer*)service)->start(); +} + +static void waitForRecoveryPress() { + pinMode(CONFIG_FLASH_GPIO, INPUT_PULLUP); + for (uint16_t i = 0; i < recovery_boot_detection_ms; i += recovery_boot_delay_ms) { + delay(recovery_boot_delay_ms); + if (digitalRead(CONFIG_FLASH_GPIO) == LOW) { + working_mode = WorkingMode::RECOVERY; + break; + } + } +} +#endif + + +void setup() { + WiFi.disconnect(); +#ifndef CONFIG_TARGET_ESP01 + homekit::main::waitForRecoveryPress(); +#endif + +#ifdef DEBUG + Serial.begin(115200); +#endif + + auto cfg = config::read(); + if (config::isDirty(cfg)) { + PRINTLN("config is dirty, erasing..."); + config::erase(cfg); +#ifdef CONFIG_TARGET_NODEMCU + led::board_led->blink(10, 50); +#else + led::mcu_led->blink(10, 50); +#endif + } + +#ifndef CONFIG_TARGET_ESP01 + switch (working_mode) { + case WorkingMode::RECOVERY: + wifiHotspot(); + break; + + case WorkingMode::NORMAL: +#endif + wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnected); + wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnected); + wifiConnect(); +#ifndef CONFIG_TARGET_ESP01 + break; + } +#endif +} + +void loop(LoopConfig* config) { +#ifndef CONFIG_TARGET_ESP01 + if (working_mode == WorkingMode::NORMAL) { +#endif + if (wifi_state == WiFiConnectionState::WAITING) { + PRINT("."); + led::mcu_led->blink(2, 50); + delay(1000); + return; + } + + if (wifi_state == WiFiConnectionState::JUST_CONNECTED) { +#ifdef CONFIG_TARGET_NODEMCU + led::board_led->blink(3, 300); +#else + led::mcu_led->blink(3, 300); +#endif + wifi_state = WiFiConnectionState::CONNECTED; + + if (service == nullptr) { + service = new mqtt::Mqtt(); + mqttDiagModule = new mqtt::MqttDiagnosticsModule(); + mqttOtaModule = new mqtt::MqttOtaModule(); + + ((mqtt::Mqtt*)service)->addModule(mqttDiagModule); + ((mqtt::Mqtt*)service)->addModule(mqttOtaModule); + + if (config != nullptr) + config->onMqttCreated(*(mqtt::Mqtt*)service); + } + + ((mqtt::Mqtt*)service)->connect(); +#if MQTT_BLINK + blinkStopWatch.save(); +#endif + } + + auto mqtt = (mqtt::Mqtt*)service; + if (static_cast(wifi_state) >= 1 && mqtt != nullptr) { + mqtt->loop(); + + if (mqttOtaModule != nullptr && mqttOtaModule->isReadyToRestart()) { + mqtt->disconnect(); + } + +#if MQTT_BLINK + // periodically blink board led + if (blinkStopWatch.elapsed(5000)) { +#ifdef CONFIG_TARGET_NODEMCU + board_led->blink(1, 10); +#endif + blinkStopWatch.save(); + } +#endif + } +#ifndef CONFIG_TARGET_ESP01 + } else { + if (dnsServer != nullptr) + dnsServer->processNextRequest(); + + auto httpServer = (HttpServer*)service; + if (httpServer != nullptr) + httpServer->loop(); + } +#endif +} + +static void onWifiConnected(const WiFiEventStationModeGotIP& event) { + PRINTF("connected (%s)\n", WiFi.localIP().toString().c_str()); + wifi_state = WiFiConnectionState::JUST_CONNECTED; +} + +static void onWifiDisconnected(const WiFiEventStationModeDisconnected& event) { + PRINTLN("disconnected from wi-fi"); + wifi_state = WiFiConnectionState::WAITING; + if (service != nullptr) + ((mqtt::Mqtt*)service)->disconnect(); + wifiTimer.once(2, wifiConnect); +} + +} \ No newline at end of file diff --git a/platformio/common/libs/main/homekit/main.h b/platformio/common/libs/main/homekit/main.h new file mode 100644 index 0000000..a503dd0 --- /dev/null +++ b/platformio/common/libs/main/homekit/main.h @@ -0,0 +1,48 @@ +#ifndef HOMEKIT_LIB_MAIN_H +#define HOMEKIT_LIB_MAIN_H + +#include +#include +#include +#include +#include + +#include +#include +#ifndef CONFIG_TARGET_ESP01 +#include +#endif +#include +#include + +#include + +namespace homekit::main { + +#ifndef CONFIG_TARGET_ESP01 +enum class WorkingMode { + RECOVERY, // AP mode, http server with configuration + NORMAL, // MQTT client +}; + +extern enum WorkingMode working_mode; +#endif + +enum class WiFiConnectionState { + WAITING = 0, + JUST_CONNECTED = 1, + CONNECTED = 2 +}; + + +struct LoopConfig { + std::function onMqttCreated; +}; + + +void setup(); +void loop(LoopConfig* config); + +} + +#endif //HOMEKIT_LIB_MAIN_H diff --git a/platformio/common/libs/main/library.json b/platformio/common/libs/main/library.json new file mode 100644 index 0000000..04eedab --- /dev/null +++ b/platformio/common/libs/main/library.json @@ -0,0 +1,12 @@ +{ + "name": "homekit_main", + "version": "1.0.8", + "build": { + "flags": "-I../../include" + }, + "dependencies": { + "homekit_mqtt_module_ota": "file://../common/libs/mqtt_module_ota", + "homekit_mqtt_module_diagnostics": "file://../common/libs/mqtt_module_diagnostics" + } +} + diff --git a/platformio/common/libs/mqtt/homekit/mqtt.cpp b/platformio/common/libs/mqtt/homekit/mqtt.cpp deleted file mode 100644 index c2ba293..0000000 --- a/platformio/common/libs/mqtt/homekit/mqtt.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "./mqtt.h" - -namespace homekit::mqtt { - - const uint8_t MQTT_CA_FINGERPRINT[] = { \ - 0x0e, 0xb6, 0x3a, 0x02, 0x1f, \ - 0x4e, 0x1e, 0xe1, 0x6a, 0x67, \ - 0x62, 0xec, 0x64, 0xd4, 0x84, \ - 0x8a, 0xb0, 0xc9, 0x9c, 0xbb \ - };; - const char MQTT_SERVER[] = "mqtt.solarmon.ru"; - const uint16_t MQTT_PORT = 8883; - const char MQTT_USERNAME[] = CONFIG_MQTT_USERNAME; - const char MQTT_PASSWORD[] = CONFIG_MQTT_PASSWORD; - const char MQTT_CLIENT_ID[] = CONFIG_MQTT_CLIENT_ID; - const char MQTT_SECRET[CONFIG_NODE_SECRET_SIZE+1] = CONFIG_NODE_SECRET; - -} diff --git a/platformio/common/libs/mqtt/homekit/mqtt.h b/platformio/common/libs/mqtt/homekit/mqtt.h deleted file mode 100644 index 2c86b00..0000000 --- a/platformio/common/libs/mqtt/homekit/mqtt.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef COMMON_HOMEKIT_MQTT_H -#define COMMON_HOMEKIT_MQTT_H - -#include - -namespace homekit::mqtt { - - extern const uint8_t MQTT_CA_FINGERPRINT[]; - extern const char MQTT_SERVER[]; - extern const uint16_t MQTT_PORT; - extern const char MQTT_USERNAME[]; - extern const char MQTT_PASSWORD[]; - extern const char MQTT_CLIENT_ID[]; - extern const char MQTT_SECRET[CONFIG_NODE_SECRET_SIZE+1]; - -} - -#endif //COMMON_HOMEKIT_MQTT_H diff --git a/platformio/common/libs/mqtt/homekit/mqtt/module.cpp b/platformio/common/libs/mqtt/homekit/mqtt/module.cpp new file mode 100644 index 0000000..e78ff12 --- /dev/null +++ b/platformio/common/libs/mqtt/homekit/mqtt/module.cpp @@ -0,0 +1,26 @@ +#include "./module.h" +#include + +namespace homekit::mqtt { + +bool MqttModule::tickElapsed() { + if (!tickSw.elapsed(tickInterval*1000)) + return false; + + tickSw.save(); + return true; +} + +void MqttModule::handlePayload(Mqtt& mqtt, String& topic, uint16_t packetId, const uint8_t* payload, size_t length, + size_t index, size_t total) { + if (length != total) + PRINTLN("mqtt: received partial message, not supported"); + + // TODO +} + +void MqttModule::handleOnPublish(uint16_t packetId) {} + +void MqttModule::handleOnDisconnect(espMqttClientTypes::DisconnectReason reason) {} + +} diff --git a/platformio/common/libs/mqtt/homekit/mqtt/module.h b/platformio/common/libs/mqtt/homekit/mqtt/module.h new file mode 100644 index 0000000..7bf2522 --- /dev/null +++ b/platformio/common/libs/mqtt/homekit/mqtt/module.h @@ -0,0 +1,47 @@ +#ifndef HOMEKIT_LIB_MQTT_MODULE_H +#define HOMEKIT_LIB_MQTT_MODULE_H + +#include "./mqtt.h" +#include "./payload.h" +#include + + +namespace homekit::mqtt { + +class Mqtt; + +class MqttModule { +protected: + bool initialized; + StopWatch tickSw; + short tickInterval; + + bool receiveOnPublish; + bool receiveOnDisconnect; + + bool tickElapsed(); + +public: + MqttModule(short _tickInterval, bool _receiveOnPublish = false, bool _receiveOnDisconnect = false) + : initialized(false) + , tickInterval(_tickInterval) + , receiveOnPublish(_receiveOnPublish) + , receiveOnDisconnect(_receiveOnDisconnect) {} + + virtual void init(Mqtt& mqtt) = 0; + virtual void tick(Mqtt& mqtt) = 0; + + virtual void handlePayload(Mqtt& mqtt, String& topic, uint16_t packetId, const uint8_t *payload, size_t length, size_t index, size_t total); + virtual void handleOnPublish(uint16_t packetId); + virtual void handleOnDisconnect(espMqttClientTypes::DisconnectReason reason); + + inline void setInitialized() { + initialized = true; + } + + friend class Mqtt; +}; + +} + +#endif //HOMEKIT_LIB_MQTT_MODULE_H \ No newline at end of file diff --git a/platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp new file mode 100644 index 0000000..9d96f9f --- /dev/null +++ b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp @@ -0,0 +1,164 @@ +#include "./mqtt.h" + +#include +#include +#include + +namespace homekit::mqtt { + +const uint8_t MQTT_CA_FINGERPRINT[] = { \ + 0x0e, 0xb6, 0x3a, 0x02, 0x1f, \ + 0x4e, 0x1e, 0xe1, 0x6a, 0x67, \ + 0x62, 0xec, 0x64, 0xd4, 0x84, \ + 0x8a, 0xb0, 0xc9, 0x9c, 0xbb \ +};; +const char MQTT_SERVER[] = "mqtt.solarmon.ru"; +const uint16_t MQTT_PORT = 8883; +const char MQTT_USERNAME[] = CONFIG_MQTT_USERNAME; +const char MQTT_PASSWORD[] = CONFIG_MQTT_PASSWORD; +const char MQTT_CLIENT_ID[] = CONFIG_MQTT_CLIENT_ID; +const char MQTT_SECRET[CONFIG_NODE_SECRET_SIZE+1] = CONFIG_NODE_SECRET; + +static const uint16_t MQTT_KEEPALIVE = 30; + +using namespace espMqttClientTypes; + +Mqtt::Mqtt() { + auto cfg = config::read(); + homeId = String(cfg.flags.node_configured ? cfg.node_id : wifi::NODE_ID); + + randomSeed(micros()); + + client.onConnect([&](bool sessionPresent) { + PRINTLN("mqtt: connected"); + + for (auto* module: modules) { + if (!module->initialized) { + module->init(*this); + module->setInitialized(); + } + } + + connected = true; + }); + + client.onDisconnect([&](DisconnectReason reason) { + PRINTF("mqtt: disconnected, reason=%d\n", static_cast(reason)); +#ifdef DEBUG + if (reason == DisconnectReason::TLS_BAD_FINGERPRINT) + PRINTLN("reason: bad fingerprint"); +#endif + + for (auto* module: modules) { + if (module->receiveOnDisconnect) { + module->handleOnDisconnect(reason); + } + } + +// if (ota.readyToRestart) { +// restartTimer.once(1, restart); +// } else { + reconnectTimer.once(2, [&]() { + reconnect(); + }); +// } + }); + + client.onSubscribe([&](uint16_t packetId, const SubscribeReturncode* returncodes, size_t len) { + PRINTF("mqtt: subscribe ack, packet_id=%d\n", packetId); + for (size_t i = 0; i < len; i++) { + PRINTF(" return code: %u\n", static_cast(*(returncodes+i))); + } + }); + + client.onUnsubscribe([&](uint16_t packetId) { + PRINTF("mqtt: unsubscribe ack, packet_id=%d\n", packetId); + }); + + client.onMessage([&](const MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) { + PRINTF("mqtt: message received, topic=%s, qos=%d, dup=%d, retain=%d, len=%ul, index=%ul, total=%ul\n", + topic, properties.qos, (int)properties.dup, (int)properties.retain, len, index, total); + + const char *ptr = topic + homeId.length() + 10; + String relevantTopic(ptr); + + auto it = moduleSubscriptions.find(relevantTopic); + if (it != moduleSubscriptions.end()) { + auto module = it->second; + module->handlePayload(*this, relevantTopic, properties.packetId, payload, len, index, total); + } else { + PRINTF("error: module subscription for topic %s not found\n", topic); + } + }); + + client.onPublish([&](uint16_t packetId) { + PRINTF("mqtt: publish ack, packet_id=%d\n", packetId); + + for (auto* module: modules) { + if (module->receiveOnPublish) { + module->handleOnPublish(packetId); + } + } + }); + + client.setServer(MQTT_SERVER, MQTT_PORT); + client.setClientId(MQTT_CLIENT_ID); + client.setCredentials(MQTT_USERNAME, MQTT_PASSWORD); + client.setCleanSession(true); + client.setFingerprint(MQTT_CA_FINGERPRINT); + client.setKeepAlive(MQTT_KEEPALIVE); +} + +void Mqtt::connect() { + reconnect(); +} + +void Mqtt::reconnect() { + if (client.connected()) { + PRINTLN("warning: already connected"); + return; + } + client.connect(); +} + +void Mqtt::disconnect() { + // TODO test how this works??? + reconnectTimer.detach(); + client.disconnect(); +} + +void Mqtt::loop() { + client.loop(); + for (auto& module: modules) { + module->tick(*this); + } +} + +uint16_t Mqtt::publish(const String& topic, uint8_t* payload, size_t length) { + String fullTopic = "hk/" + homeId + "/temphum/" + topic; + return client.publish(fullTopic.c_str(), 1, false, payload, length); +} + +uint16_t Mqtt::subscribe(const String& topic, uint8_t qos) { + String fullTopic = "hk/" + homeId + "/temphum/" + topic; + PRINTF("mqtt: subscribing to %s...\n", fullTopic.c_str()); + + uint16_t packetId = client.subscribe(fullTopic.c_str(), qos); + if (!packetId) + PRINTF("error: failed to subscribe to %s\n", fullTopic.c_str()); + return packetId; +} + +void Mqtt::addModule(MqttModule* module) { + modules.emplace_back(module); + if (connected) { + module->init(*this); + module->setInitialized(); + } +} + +void Mqtt::subscribeModule(String& topic, MqttModule* module) { + moduleSubscriptions[topic] = module; +} + +} diff --git a/platformio/common/libs/mqtt/homekit/mqtt/mqtt.h b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.h new file mode 100644 index 0000000..983cc5a --- /dev/null +++ b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.h @@ -0,0 +1,48 @@ +#ifndef HOMEKIT_LIB_MQTT_H +#define HOMEKIT_LIB_MQTT_H + +#include +#include +#include +#include +#include +#include "./module.h" + +namespace homekit::mqtt { + +extern const uint8_t MQTT_CA_FINGERPRINT[]; +extern const char MQTT_SERVER[]; +extern const uint16_t MQTT_PORT; +extern const char MQTT_USERNAME[]; +extern const char MQTT_PASSWORD[]; +extern const char MQTT_CLIENT_ID[]; +extern const char MQTT_SECRET[CONFIG_NODE_SECRET_SIZE+1]; + +class MqttModule; + +class Mqtt { +private: + String homeId; + WiFiClientSecure httpsSecureClient; + espMqttClientSecure client; + Ticker reconnectTimer; + std::vector modules; + std::map moduleSubscriptions; + bool connected; + + uint16_t subscribe(const String& topic, uint8_t qos = 0); + +public: + Mqtt(); + void connect(); + void disconnect(); + void reconnect(); + void loop(); + void addModule(MqttModule* module); + void subscribeModule(String& topic, MqttModule* module); + uint16_t publish(const String& topic, uint8_t* payload, size_t length); +}; + +} + +#endif //HOMEKIT_LIB_MQTT_H \ No newline at end of file diff --git a/platformio/common/libs/mqtt/homekit/mqtt/payload.h b/platformio/common/libs/mqtt/homekit/mqtt/payload.h new file mode 100644 index 0000000..3e0fe0c --- /dev/null +++ b/platformio/common/libs/mqtt/homekit/mqtt/payload.h @@ -0,0 +1,15 @@ +#ifndef HOMEKIT_MQTT_PAYLOAD_H +#define HOMEKIT_MQTT_PAYLOAD_H + +#include + +namespace homekit::mqtt { + +struct MqttPayload { + virtual ~MqttPayload() = default; + virtual size_t size() const = 0; +}; + +} + +#endif \ No newline at end of file diff --git a/platformio/common/libs/mqtt/library.json b/platformio/common/libs/mqtt/library.json index 8131494..179b10a 100644 --- a/platformio/common/libs/mqtt/library.json +++ b/platformio/common/libs/mqtt/library.json @@ -1,8 +1,7 @@ { "name": "homekit_mqtt", - "version": "1.0.2", + "version": "1.0.8", "build": { "flags": "-I../../include" } } - diff --git a/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.cpp b/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.cpp new file mode 100644 index 0000000..f022f79 --- /dev/null +++ b/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.cpp @@ -0,0 +1,47 @@ +#include "./diagnostics.h" +#include +#include + +namespace homekit::mqtt { + +static const char TOPIC_DIAGNOSTICS[] = "stat"; +static const char TOPIC_INITIAL_DIAGNOSTICS[] = "stat1"; + +void MqttDiagnosticsModule::init(Mqtt& mqtt) {} + +void MqttDiagnosticsModule::tick(Mqtt& mqtt) { + if (!tickElapsed()) + return; + + auto cfg = config::read(); + + if (!initialSent) { + MqttInitialDiagnosticsPayload stat{ + .ip = wifi::getIPAsInteger(), + .fw_version = CONFIG_FW_VERSION, + .rssi = wifi::getRSSI(), + .free_heap = ESP.getFreeHeap(), + .flags = DiagnosticsFlags{ + .state = 1, + .config_changed_value_present = 1, + .config_changed = static_cast(cfg.flags.node_configured || + cfg.flags.wifi_configured ? 1 : 0) + } + }; + mqtt.publish(TOPIC_INITIAL_DIAGNOSTICS, reinterpret_cast(&stat), sizeof(stat)); + initialSent = true; + } else { + MqttDiagnosticsPayload stat{ + .rssi = wifi::getRSSI(), + .free_heap = ESP.getFreeHeap(), + .flags = DiagnosticsFlags{ + .state = 1, + .config_changed_value_present = 0, + .config_changed = 0 + } + }; + mqtt.publish(TOPIC_DIAGNOSTICS, reinterpret_cast(&stat), sizeof(stat)); + } +} + +} \ No newline at end of file diff --git a/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.h b/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.h new file mode 100644 index 0000000..055c179 --- /dev/null +++ b/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.h @@ -0,0 +1,46 @@ +#ifndef HOMEKIT_LIB_MQTT_MODULE_DIAGNOSTICS_H +#define HOMEKIT_LIB_MQTT_MODULE_DIAGNOSTICS_H + +#include +#include + +namespace homekit::mqtt { + +struct DiagnosticsFlags { + uint8_t state: 1; + uint8_t config_changed_value_present: 1; + uint8_t config_changed: 1; + uint8_t reserved: 5; +} __attribute__((packed)); + +struct MqttInitialDiagnosticsPayload { + uint32_t ip; + uint8_t fw_version; + int8_t rssi; + uint32_t free_heap; + DiagnosticsFlags flags; +} __attribute__((packed)); + +struct MqttDiagnosticsPayload { + int8_t rssi; + uint32_t free_heap; + DiagnosticsFlags flags; +} __attribute__((packed)); + + +class MqttDiagnosticsModule: public MqttModule { +private: + bool initialSent; + +public: + MqttDiagnosticsModule() + : MqttModule(30) + , initialSent(false) {} + + void init(Mqtt& mqtt) override; + void tick(Mqtt& mqtt) override; +}; + +} + +#endif //HOMEKIT_LIB_MQTT_MODULE_DIAGNOSTICS_H diff --git a/platformio/common/libs/mqtt_module_diagnostics/library.json b/platformio/common/libs/mqtt_module_diagnostics/library.json new file mode 100644 index 0000000..81bf0fb --- /dev/null +++ b/platformio/common/libs/mqtt_module_diagnostics/library.json @@ -0,0 +1,7 @@ +{ + "name": "homekit_mqtt_module_diagnostics", + "version": "1.0.0", + "build": { + "flags": "-I../../include" + } +} diff --git a/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.cpp b/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.cpp new file mode 100644 index 0000000..2f5f814 --- /dev/null +++ b/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.cpp @@ -0,0 +1,162 @@ +#include "./ota.h" +#include +#include +#include + +namespace homekit::mqtt { + +using homekit::led::mcu_led; + +#define MD5_SIZE 16 + +static const char TOPIC_OTA[] = "ota"; +static const char TOPIC_OTA_RESPONSE[] = "otares"; + +void MqttOtaModule::init(Mqtt& mqtt) { + String topic(TOPIC_OTA); + mqtt.subscribeModule(topic, this); +} + +void MqttOtaModule::tick(Mqtt& mqtt) { + if (!tickElapsed()) + return; +} + +void MqttOtaModule::handlePayload(Mqtt& mqtt, String& topic, uint16_t packetId, const uint8_t *payload, size_t length, size_t index, size_t total) { + char md5[33]; + char* md5Ptr = md5; + + if (index != 0 && ota.dataPacketId != packetId) { + PRINTLN("mqtt/ota: non-matching packet id"); + return; + } + + Update.runAsync(true); + + if (index == 0) { + if (length < CONFIG_NODE_SECRET_SIZE + MD5_SIZE) { + PRINTLN("mqtt/ota: failed to check secret, first packet size is too small"); + return; + } + + if (memcmp((const char*)payload, CONFIG_NODE_SECRET, CONFIG_NODE_SECRET_SIZE) != 0) { + PRINTLN("mqtt/ota: invalid secret"); + return; + } + + PRINTF("mqtt/ota: starting update, total=%ul\n", total-CONFIG_NODE_SECRET_SIZE); + for (int i = 0; i < MD5_SIZE; i++) { + md5Ptr += sprintf(md5Ptr, "%02x", *((unsigned char*)(payload+CONFIG_NODE_SECRET_SIZE+i))); + } + md5[32] = '\0'; + PRINTF("mqtt/ota: md5 is %s\n", md5); + PRINTF("mqtt/ota: first packet is %ul bytes length\n", length); + + md5[32] = '\0'; + + if (Update.isRunning()) { + Update.end(); + Update.clearError(); + } + + if (!Update.setMD5(md5)) { + PRINTLN("mqtt/ota: setMD5 failed"); + return; + } + + ota.dataPacketId = packetId; + + if (!Update.begin(total - CONFIG_NODE_SECRET_SIZE - MD5_SIZE)) { + ota.clean(); +#ifdef DEBUG + Update.printError(Serial); +#endif + sendResponse(mqtt, OtaResult::UPDATE_ERROR, Update.getError()); + } + + ota.written = Update.write(const_cast(payload)+CONFIG_NODE_SECRET_SIZE + MD5_SIZE, length-CONFIG_NODE_SECRET_SIZE - MD5_SIZE); + ota.written += CONFIG_NODE_SECRET_SIZE + MD5_SIZE; + + mcu_led->blink(1, 1); + PRINTF("mqtt/ota: updating %u/%u\n", ota.written, Update.size()); + + } else { + if (!Update.isRunning()) { + PRINTLN("mqtt/ota: update is not running"); + return; + } + + if (index == ota.written) { + size_t written; + if ((written = Update.write(const_cast(payload), length)) != length) { + PRINTF("mqtt/ota: error: tried to write %ul bytes, write() returned %ul\n", + length, written); + ota.clean(); + Update.end(); + Update.clearError(); + sendResponse(mqtt, OtaResult::WRITE_ERROR); + return; + } + ota.written += length; + + mcu_led->blink(1, 1); + PRINTF("mqtt/ota: updating %u/%u\n", + ota.written - CONFIG_NODE_SECRET_SIZE - MD5_SIZE, + Update.size()); + } else { + PRINTF("mqtt/ota: position is invalid, expected %ul, got %ul\n", ota.written, index); + ota.clean(); + Update.end(); + Update.clearError(); + } + } + + if (Update.isFinished()) { + ota.dataPacketId = 0; + + if (Update.end()) { + ota.finished = true; + ota.publishResultPacketId = sendResponse(mqtt, OtaResult::OK); + PRINTF("mqtt/ota: ok, otares packet_id=%d\n", ota.publishResultPacketId); + } else { + ota.clean(); + + PRINTF("mqtt/ota: error: %u\n", Update.getError()); +#ifdef DEBUG + Update.printError(Serial); +#endif + Update.clearError(); + + sendResponse(mqtt, OtaResult::UPDATE_ERROR, Update.getError()); + } + } +} + +uint16_t MqttOtaModule::sendResponse(Mqtt& mqtt, OtaResult status, uint8_t error_code) const { + MqttOtaResponsePayload resp{ + .status = status, + .error_code = error_code + }; + return mqtt.publish(TOPIC_OTA_RESPONSE, reinterpret_cast(&resp), sizeof(resp)); +} + +void MqttOtaModule::handleOnDisconnect(espMqttClientTypes::DisconnectReason reason) { + if (ota.started()) { + PRINTLN("mqtt: update was in progress, canceling.."); + ota.clean(); + Update.end(); + Update.clearError(); + } + + if (ota.readyToRestart) { + restartTimer.once(1, restart); + } +} + +void MqttOtaModule::handleOnPublish(uint16_t packetId) { + if (ota.finished && packetId == ota.publishResultPacketId) { + ota.readyToRestart = true; + } +} + +} diff --git a/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.h b/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.h new file mode 100644 index 0000000..53613c3 --- /dev/null +++ b/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.h @@ -0,0 +1,72 @@ +#ifndef HOMEKIT_LIB_MQTT_MODULE_OTA_H +#define HOMEKIT_LIB_MQTT_MODULE_OTA_H + +#include +#include +#include + +namespace homekit::mqtt { + +enum class OtaResult: uint8_t { + OK = 0, + UPDATE_ERROR = 1, + WRITE_ERROR = 2, +}; + +struct OtaStatus { + uint16_t dataPacketId; + uint16_t publishResultPacketId; + bool finished; + bool readyToRestart; + size_t written; + + OtaStatus() + : dataPacketId(0) + , publishResultPacketId(0) + , finished(false) + , readyToRestart(false) + , written(0) + {} + + inline void clean() { + dataPacketId = 0; + publishResultPacketId = 0; + finished = false; + readyToRestart = false; + written = 0; + } + + inline bool started() const { + return dataPacketId != 0; + } +}; + +struct MqttOtaResponsePayload { + OtaResult status; + uint8_t error_code; +} __attribute__((packed)); + + +class MqttOtaModule: public MqttModule { +private: + OtaStatus ota; + Ticker restartTimer; + + uint16_t sendResponse(Mqtt& mqtt, OtaResult status, uint8_t error_code = 0) const; + +public: + MqttOtaModule() : MqttModule(0, true, true) {} + + void init(Mqtt& mqtt) override; + void tick(Mqtt& mqtt) override; + void handlePayload(Mqtt& mqtt, String& topic, uint16_t packetId, const uint8_t *payload, size_t length, size_t index, size_t total) override; + void handleOnPublish(uint16_t packetId) override; + void handleOnDisconnect(espMqttClientTypes::DisconnectReason reason) override; + inline bool isReadyToRestart() const { + return ota.readyToRestart; + } +}; + +} + +#endif //HOMEKIT_LIB_MQTT_MODULE_OTA_H diff --git a/platformio/common/libs/mqtt_module_ota/library.json b/platformio/common/libs/mqtt_module_ota/library.json new file mode 100644 index 0000000..1eeda1b --- /dev/null +++ b/platformio/common/libs/mqtt_module_ota/library.json @@ -0,0 +1,10 @@ +{ + "name": "homekit_mqtt_module_ota", + "version": "1.0.1", + "build": { + "flags": "-I../../include" + }, + "dependencies": { + "homekit_led": "file://../common/libs/led" + } +} diff --git a/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.cpp b/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.cpp new file mode 100644 index 0000000..8b7a2ef --- /dev/null +++ b/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.cpp @@ -0,0 +1,23 @@ +#include "temphum.h" + +namespace homekit::mqtt { + +static const char TOPIC_TEMPHUM_DATA[] = "data"; + +void MqttTemphumModule::init(Mqtt &mqtt) {} + +void MqttTemphumModule::tick(homekit::mqtt::Mqtt& mqtt) { + if (!tickElapsed()) + return; + + temphum::SensorData sd = sensor->read(); + MqttTemphumPayload payload { + .temp = sd.temp, + .rh = sd.rh, + .error = sd.error + }; + + mqtt.publish(TOPIC_TEMPHUM_DATA, reinterpret_cast(&payload), sizeof(payload)); +} + +} \ No newline at end of file diff --git a/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.h b/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.h new file mode 100644 index 0000000..5c41cef --- /dev/null +++ b/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.h @@ -0,0 +1,28 @@ +#ifndef HOMEKIT_LIB_MQTT_MODULE_TEMPHUM_H +#define HOMEKIT_LIB_MQTT_MODULE_TEMPHUM_H + +#include +#include + +namespace homekit::mqtt { + +struct MqttTemphumPayload { + double temp = 0; + double rh = 0; + uint8_t error = 0; +} __attribute__((packed)); + + +class MqttTemphumModule : public MqttModule { +private: + temphum::Sensor* sensor; + +public: + MqttTemphumModule(temphum::Sensor* _sensor) : MqttModule(10), sensor(_sensor) {} + void init(Mqtt& mqtt) override; + void tick(Mqtt& mqtt) override; +}; + +} + +#endif //HOMEKIT_LIB_MQTT_MODULE_TEMPHUM_H diff --git a/platformio/common/libs/mqtt_module_temphum/library.json b/platformio/common/libs/mqtt_module_temphum/library.json new file mode 100644 index 0000000..30f5eb6 --- /dev/null +++ b/platformio/common/libs/mqtt_module_temphum/library.json @@ -0,0 +1,10 @@ +{ + "name": "homekit_mqtt_module_temphum", + "version": "1.0.8", + "build": { + "flags": "-I../../include" + }, + "dependencies": { + "homekit_temphum": "file://../common/libs/temphum" + } +} diff --git a/platformio/common/libs/temphum/homekit/temphum.cpp b/platformio/common/libs/temphum/homekit/temphum.cpp new file mode 100644 index 0000000..ac4d371 --- /dev/null +++ b/platformio/common/libs/temphum/homekit/temphum.cpp @@ -0,0 +1,87 @@ +#ifndef CONFIG_TARGET_ESP01 +#include +#endif +#include +#include "temphum.h" + +namespace homekit::temphum { + +void Sensor::setup() const { +#ifndef CONFIG_TARGET_ESP01 + pinMode(CONFIG_SDA_GPIO, OUTPUT); + pinMode(CONFIG_SCL_GPIO, OUTPUT); + + Wire.begin(CONFIG_SDA_GPIO, CONFIG_SCL_GPIO); +#else + Wire.begin(); +#endif +} + +void Sensor::writeCommand(int reg) const { + Wire.beginTransmission(dev_addr); + Wire.write(reg); + Wire.endTransmission(); + delay(500); // wait for the measurement to be ready +} + +SensorData Si7021::read() { + uint8_t error = 0; + writeCommand(0xf3); // command to measure temperature + Wire.requestFrom(dev_addr, 2); + if (Wire.available() < 2) { + PRINTLN("Si7021: 0xf3: could not read 2 bytes"); + } + uint16_t temp_raw = Wire.read() << 8 | Wire.read(); + double temperature = ((175.72 * temp_raw) / 65536.0) - 46.85; + + writeCommand(0xf5); // command to measure humidity + Wire.requestFrom(dev_addr, 2); + if (Wire.available() < 2) { + PRINTLN("Si7021: 0xf5: could not read 2 bytes"); + } + uint16_t hum_raw = Wire.read() << 8 | Wire.read(); + double humidity = ((125.0 * hum_raw) / 65536.0) - 6.0; + + return { + .error = error, + .temp = temperature, + .rh = humidity + }; +} + +SensorData DHT12::read() { + SensorData sd; + byte raw[5]; + sd.error = 1; + + writeCommand(0); + Wire.requestFrom(dev_addr, 5); + + if (Wire.available() < 5) { + PRINTLN("DHT12: could not read 5 bytes"); + goto end; + } + + // Parse the received data + for (uint8_t i = 0; i < 5; i++) + raw[i] = Wire.read(); + + if (((raw[0] + raw[1] + raw[2] + raw[3]) & 0xff) != raw[4]) { + PRINTLN("DHT12: checksum error"); + goto end; + } + + // Calculate temperature and humidity values + sd.temp = raw[2] + (raw[3] & 0x7f) * 0.1; + if (raw[3] & 0x80) + sd.temp *= -1; + + sd.rh = raw[0] + raw[1] * 0.1; + + sd.error = 0; + +end: + return sd; +} + +} \ No newline at end of file diff --git a/platformio/common/libs/temphum/homekit/temphum.h b/platformio/common/libs/temphum/homekit/temphum.h new file mode 100644 index 0000000..1952ce0 --- /dev/null +++ b/platformio/common/libs/temphum/homekit/temphum.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +namespace homekit::temphum { + +struct SensorData { + uint8_t error = 0; + double temp = 0; // celsius + double rh = 0; // relative humidity percentage +}; + + +class Sensor { +protected: + int dev_addr; +public: + explicit Sensor(int dev) : dev_addr(dev) {} + void setup() const; + void writeCommand(int reg) const; + virtual SensorData read() = 0; +}; + + +class Si7021 : public Sensor { +public: + SensorData read() override; + Si7021() : Sensor(0x40) {} +}; + + +class DHT12 : public Sensor { +public: + SensorData read() override; + DHT12() : Sensor(0x5c) {} +}; + +} \ No newline at end of file diff --git a/platformio/common/libs/temphum/library.json b/platformio/common/libs/temphum/library.json new file mode 100644 index 0000000..ae7f670 --- /dev/null +++ b/platformio/common/libs/temphum/library.json @@ -0,0 +1,8 @@ +{ + "name": "homekit_temphum", + "version": "1.0.2", + "build": { + "flags": "-I../../include" + } +} + diff --git a/platformio/dumb_mqtt/.gitignore b/platformio/dumb_mqtt/.gitignore new file mode 100644 index 0000000..3fe18ad --- /dev/null +++ b/platformio/dumb_mqtt/.gitignore @@ -0,0 +1,3 @@ +.pio +CMakeListsPrivate.txt +cmake-build-*/ diff --git a/platformio/dumb_mqtt/src/main.cpp b/platformio/dumb_mqtt/src/main.cpp new file mode 100644 index 0000000..eefc165 --- /dev/null +++ b/platformio/dumb_mqtt/src/main.cpp @@ -0,0 +1,12 @@ +#include +#include + +using namespace homekit; + +void setup() { + main::setup(); +} + +void loop() { + main::loop(nullptr); +} diff --git a/platformio/temphum/src/leds.cpp b/platformio/temphum/src/leds.cpp deleted file mode 100644 index f0c9d51..0000000 --- a/platformio/temphum/src/leds.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "leds.h" - -namespace homekit { - -#ifdef CONFIG_TARGET_NODEMCU -Led* board_led = new Led(CONFIG_BOARD_LED_GPIO); -#endif -Led* mcu_led = new Led(CONFIG_MCU_LED_GPIO); - -} - diff --git a/platformio/temphum/src/leds.h b/platformio/temphum/src/leds.h deleted file mode 100644 index 04c1f79..0000000 --- a/platformio/temphum/src/leds.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef HOMEKIT_TEMPHUM_LEDS_H -#define HOMEKIT_TEMPHUM_LEDS_H - -#include - -namespace homekit { - -#ifdef CONFIG_TARGET_NODEMCU -extern Led* board_led; -#endif -extern Led* mcu_led; - -} - -#endif //HOMEKIT_TEMPHUM_LEDS_H \ No newline at end of file diff --git a/platformio/temphum/src/main.cpp b/platformio/temphum/src/main.cpp index 6e878a1..2df8638 100644 --- a/platformio/temphum/src/main.cpp +++ b/platformio/temphum/src/main.cpp @@ -1,109 +1,26 @@ #include -#include -#include -#include #include - -#include -#include -#ifndef CONFIG_TARGET_ESP01 -#include -#endif -#include - -#include "mqtt.h" -#include "leds.h" -#include "temphum.h" +#include +#include +#include +#include using namespace homekit; +using main::LoopConfig; +using mqtt::Mqtt; +using mqtt::MqttTemphumModule; -#ifndef CONFIG_TARGET_ESP01 -enum class WorkingMode { - RECOVERY, // AP mode, http server with configuration - NORMAL, // MQTT client -}; -static enum WorkingMode working_mode = WorkingMode::NORMAL; +temphum::Sensor* sensor = nullptr; +MqttTemphumModule* mqttTemphumModule = nullptr; -static const uint16_t recovery_boot_detection_ms = 2000; -static const uint8_t recovery_boot_delay_ms = 100; -#endif +static void onMqttCreated(Mqtt& mqtt); -enum class WiFiConnectionState { - WAITING = 0, - JUST_CONNECTED = 1, - CONNECTED = 2 +LoopConfig loopConfig = { + .onMqttCreated = onMqttCreated }; -static volatile enum WiFiConnectionState wifi_state = WiFiConnectionState::WAITING; -static void* service = nullptr; -static WiFiEventHandler wifiConnectHandler, wifiDisconnectHandler; -static Ticker wifiTimer; -temphum::BaseSensor* sensor = nullptr; - -#if MQTT_BLINK -static StopWatch blinkStopWatch; -#endif - -#ifndef CONFIG_TARGET_ESP01 -static DNSServer* dnsServer = nullptr; -#endif - -static void onWifiConnected(const WiFiEventStationModeGotIP& event); -static void onWifiDisconnected(const WiFiEventStationModeDisconnected& event); - -static void wifiConnect() { - const char *ssid, *psk, *hostname; - auto cfg = config::read(); - wifi::getConfig(cfg, &ssid, &psk, &hostname); - - PRINTF("Wi-Fi STA creds: ssid=%s, psk=%s, hostname=%s\n", ssid, psk, hostname); - - wifi_state = WiFiConnectionState::WAITING; - - WiFi.mode(WIFI_STA); - WiFi.hostname(hostname); - WiFi.begin(ssid, psk); - - PRINT("connecting to wifi.."); -} - -#ifndef CONFIG_TARGET_ESP01 -static void wifiHotspot() { - mcu_led->on(); - - auto scanResults = wifi::scan(); - - WiFi.mode(WIFI_AP); - WiFi.softAP(wifi::AP_SSID); - - dnsServer = new DNSServer(); - dnsServer->start(53, "*", WiFi.softAPIP()); - - service = new HttpServer(scanResults); - ((HttpServer*)service)->start(); -} - -static void waitForRecoveryPress() { - pinMode(CONFIG_FLASH_GPIO, INPUT_PULLUP); - for (uint16_t i = 0; i < recovery_boot_detection_ms; i += recovery_boot_delay_ms) { - delay(recovery_boot_delay_ms); - if (digitalRead(CONFIG_FLASH_GPIO) == LOW) { - working_mode = WorkingMode::RECOVERY; - break; - } - } -} -#endif - void setup() { - WiFi.disconnect(); -#ifndef CONFIG_TARGET_ESP01 - waitForRecoveryPress(); -#endif - -#ifdef DEBUG - Serial.begin(115200); -#endif + main::setup(); #if CONFIG_MODULE == HOMEKIT_SI7021 sensor = new temphum::Si7021(); @@ -111,111 +28,15 @@ void setup() { sensor = new temphum::DHT12(); #endif sensor->setup(); - - auto cfg = config::read(); - if (config::isDirty(cfg)) { - PRINTLN("config is dirty, erasing..."); - config::erase(cfg); -#ifdef CONFIG_TARGET_NODEMCU - board_led->blink(10, 50); -#else - mcu_led->blink(10, 50); -#endif - } - -#ifndef CONFIG_TARGET_ESP01 - switch (working_mode) { - case WorkingMode::RECOVERY: - wifiHotspot(); - break; - - case WorkingMode::NORMAL: -#endif - wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnected); - wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnected); - wifiConnect(); -#ifndef CONFIG_TARGET_ESP01 - break; - } -#endif } void loop() { -#ifndef CONFIG_TARGET_ESP01 - if (working_mode == WorkingMode::NORMAL) { -#endif - if (wifi_state == WiFiConnectionState::WAITING) { - PRINT("."); - mcu_led->blink(2, 50); - delay(1000); - return; - } - - if (wifi_state == WiFiConnectionState::JUST_CONNECTED) { -#ifdef CONFIG_TARGET_NODEMCU - board_led->blink(3, 300); -#endif - wifi_state = WiFiConnectionState::CONNECTED; - - if (service == nullptr) - service = new mqtt::MQTT(); - - ((mqtt::MQTT*)service)->connect(); -#if MQTT_BLINK - blinkStopWatch.save(); -#endif - } - - auto mqtt = (mqtt::MQTT*)service; - if (static_cast(wifi_state) >= 1 && mqtt != nullptr) { - mqtt->loop(); - - if (mqtt->ota.readyToRestart) { - mqtt->disconnect(); - - } else if (mqtt->diagnosticsStopWatch.elapsed(10000)) { - mqtt->sendDiagnostics(); - - auto data = sensor->read(); - PRINT("temp:"); - PRINT(data.temp); - PRINT(", rh: "); - PRINTLN(data.rh); - - mqtt->sendTempHumData(data.temp, data.rh); - } - -#if MQTT_BLINK - // periodically blink board led - if (blinkStopWatch.elapsed(5000)) { -#ifdef CONFIG_TARGET_NODEMCU - board_led->blink(1, 10); -#endif - blinkStopWatch.save(); - } -#endif - } -#ifndef CONFIG_TARGET_ESP01 - } else { - if (dnsServer != nullptr) - dnsServer->processNextRequest(); - - auto httpServer = (HttpServer*)service; - if (httpServer != nullptr) - httpServer->loop(); - } -#endif + main::loop(&loopConfig); } -static void onWifiConnected(const WiFiEventStationModeGotIP& event) { - PRINTF("connected (%s)\n", WiFi.localIP().toString().c_str()); - wifi_state = WiFiConnectionState::JUST_CONNECTED; -} - -static void onWifiDisconnected(const WiFiEventStationModeDisconnected& event) { - PRINTLN("disconnected from wi-fi"); - wifi_state = WiFiConnectionState::WAITING; - if (service != nullptr) - ((mqtt::MQTT*)service)->disconnect(); - wifiTimer.once(2, wifiConnect); +static void onMqttCreated(Mqtt& mqtt) { + if (mqttTemphumModule == nullptr) { + mqttTemphumModule = new MqttTemphumModule(sensor); + mqtt.addModule(mqttTemphumModule); + } } \ No newline at end of file diff --git a/platformio/temphum/src/mqtt.cpp b/platformio/temphum/src/mqtt.cpp deleted file mode 100644 index e912f35..0000000 --- a/platformio/temphum/src/mqtt.cpp +++ /dev/null @@ -1,316 +0,0 @@ -#include -#include -#include -#include -#include - -#include "mqtt.h" -#include "leds.h" - -namespace homekit::mqtt { - -static const char TOPIC_DIAGNOSTICS[] = "stat"; -static const char TOPIC_INITIAL_DIAGNOSTICS[] = "stat1"; -static const char TOPIC_OTA_RESPONSE[] = "otares"; -static const char TOPIC_TEMPHUM_DATA[] = "data"; -static const char TOPIC_ADMIN_OTA[] = "admin/ota"; -static const uint16_t MQTT_KEEPALIVE = 30; - -enum class IncomingMessage { - UNKNOWN, - OTA -}; - -using namespace espMqttClientTypes; - -#define MD5_SIZE 16 - -MQTT::MQTT() { - auto cfg = config::read(); - homeId = String(cfg.flags.node_configured ? cfg.node_id : wifi::NODE_ID); - - randomSeed(micros()); - - client.onConnect([&](bool sessionPresent) { - PRINTLN("mqtt: connected"); - - sendInitialDiagnostics(); - subscribe(TOPIC_ADMIN_OTA); - }); - - client.onDisconnect([&](DisconnectReason reason) { - PRINTF("mqtt: disconnected, reason=%d\n", static_cast(reason)); -#ifdef DEBUG - if (reason == DisconnectReason::TLS_BAD_FINGERPRINT) - PRINTLN("reason: bad fingerprint"); -#endif - - if (ota.started()) { - PRINTLN("mqtt: update was in progress, canceling.."); - ota.clean(); - Update.end(); - Update.clearError(); - } - - if (ota.readyToRestart) { - restartTimer.once(1, restart); - } else { - reconnectTimer.once(2, [&]() { - reconnect(); - }); - } - }); - - client.onSubscribe([&](uint16_t packetId, const SubscribeReturncode* returncodes, size_t len) { - PRINTF("mqtt: subscribe ack, packet_id=%d\n", packetId); - for (size_t i = 0; i < len; i++) { - PRINTF(" return code: %u\n", static_cast(*(returncodes+i))); - } - }); - - client.onUnsubscribe([&](uint16_t packetId) { - PRINTF("mqtt: unsubscribe ack, packet_id=%d\n", packetId); - }); - - client.onMessage([&](const MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) { - PRINTF("mqtt: message received, topic=%s, qos=%d, dup=%d, retain=%d, len=%ul, index=%ul, total=%ul\n", - topic, properties.qos, (int)properties.dup, (int)properties.retain, len, index, total); - - IncomingMessage msgType = IncomingMessage::UNKNOWN; - - const char *ptr = topic + homeId.length() + 10; - String relevantTopic(ptr); - - if (relevantTopic == TOPIC_ADMIN_OTA) - msgType = IncomingMessage::OTA; - - if (len != total && msgType != IncomingMessage::OTA) { - PRINTLN("mqtt: received partial message, not supported"); - return; - } - - switch (msgType) { - case IncomingMessage::OTA: - if (ota.finished) - break; - handleAdminOtaPayload(properties.packetId, payload, len, index, total); - break; - - case IncomingMessage::UNKNOWN: - PRINTF("error: invalid topic %s\n", topic); - break; - } - }); - - client.onPublish([&](uint16_t packetId) { - PRINTF("mqtt: publish ack, packet_id=%d\n", packetId); - - if (ota.finished && packetId == ota.publishResultPacketId) { - ota.readyToRestart = true; - } - }); - - client.setServer(MQTT_SERVER, MQTT_PORT); - client.setClientId(MQTT_CLIENT_ID); - client.setCredentials(MQTT_USERNAME, MQTT_PASSWORD); - client.setCleanSession(true); - client.setFingerprint(MQTT_CA_FINGERPRINT); - client.setKeepAlive(MQTT_KEEPALIVE); -} - -void MQTT::connect() { - reconnect(); -} - -void MQTT::reconnect() { - if (client.connected()) { - PRINTLN("warning: already connected"); - return; - } - client.connect(); -} - -void MQTT::disconnect() { - // TODO test how this works??? - reconnectTimer.detach(); - client.disconnect(); -} - -uint16_t MQTT::publish(const String &topic, uint8_t *payload, size_t length) { - String fullTopic = "hk/" + homeId + "/temphum/" + topic; - return client.publish(fullTopic.c_str(), 1, false, payload, length); -} - -void MQTT::loop() { - client.loop(); -} - -uint16_t MQTT::subscribe(const String &topic, uint8_t qos) { - String fullTopic = "hk/" + homeId + "/temphum/" + topic; - PRINTF("mqtt: subscribing to %s...\n", fullTopic.c_str()); - - uint16_t packetId = client.subscribe(fullTopic.c_str(), qos); - if (!packetId) - PRINTF("error: failed to subscribe to %s\n", fullTopic.c_str()); - return packetId; -} - -void MQTT::sendInitialDiagnostics() { - auto cfg = config::read(); - InitialDiagnosticsPayload stat{ - .ip = wifi::getIPAsInteger(), - .fw_version = CONFIG_FW_VERSION, - .rssi = wifi::getRSSI(), - .free_heap = ESP.getFreeHeap(), - .flags = DiagnosticsFlags{ - .state = 1, - .config_changed_value_present = 1, - .config_changed = static_cast(cfg.flags.node_configured || - cfg.flags.wifi_configured ? 1 : 0) - } - }; - publish(TOPIC_INITIAL_DIAGNOSTICS, reinterpret_cast(&stat), sizeof(stat)); - diagnosticsStopWatch.save(); -} - -void MQTT::sendDiagnostics() { - DiagnosticsPayload stat{ - .rssi = wifi::getRSSI(), - .free_heap = ESP.getFreeHeap(), - .flags = DiagnosticsFlags{ - .state = 1, - .config_changed_value_present = 0, - .config_changed = 0 - } - }; - publish(TOPIC_DIAGNOSTICS, reinterpret_cast(&stat), sizeof(stat)); - diagnosticsStopWatch.save(); -} - -void MQTT::sendTempHumData(double temp, double rh) { - TempHumDataPayload data { - .temp = temp, - .rh = rh - }; - publish(TOPIC_TEMPHUM_DATA, reinterpret_cast(&data), sizeof(data)); -} - -uint16_t MQTT::sendOtaResponse(OTAResult status, uint8_t error_code) { - OTAResponse resp{ - .status = status, - .error_code = error_code - }; - return publish(TOPIC_OTA_RESPONSE, reinterpret_cast(&resp), sizeof(resp)); -} - -void MQTT::handleAdminOtaPayload(uint16_t packetId, const uint8_t *payload, size_t length, size_t index, size_t total) { - char md5[33]; - char* md5Ptr = md5; - - if (index != 0 && ota.dataPacketId != packetId) { - PRINTLN("mqtt/ota: non-matching packet id"); - return; - } - - Update.runAsync(true); - - if (index == 0) { - if (length < CONFIG_NODE_SECRET_SIZE + MD5_SIZE) { - PRINTLN("mqtt/ota: failed to check secret, first packet size is too small"); - return; - } - - if (memcmp((const char*)payload, CONFIG_NODE_SECRET, CONFIG_NODE_SECRET_SIZE) != 0) { - PRINTLN("mqtt/ota: invalid secret"); - return; - } - - PRINTF("mqtt/ota: starting update, total=%ul\n", total-CONFIG_NODE_SECRET_SIZE); - for (int i = 0; i < MD5_SIZE; i++) { - md5Ptr += sprintf(md5Ptr, "%02x", *((unsigned char*)(payload+CONFIG_NODE_SECRET_SIZE+i))); - } - md5[32] = '\0'; - PRINTF("mqtt/ota: md5 is %s\n", md5); - PRINTF("mqtt/ota: first packet is %ul bytes length\n", length); - - md5[32] = '\0'; - - if (Update.isRunning()) { - Update.end(); - Update.clearError(); - } - - if (!Update.setMD5(md5)) { - PRINTLN("mqtt/ota: setMD5 failed"); - return; - } - - ota.dataPacketId = packetId; - - if (!Update.begin(total - CONFIG_NODE_SECRET_SIZE - MD5_SIZE)) { - ota.clean(); -#ifdef DEBUG - Update.printError(Serial); -#endif - sendOtaResponse(OTAResult::UPDATE_ERROR, Update.getError()); - } - - ota.written = Update.write(const_cast(payload)+CONFIG_NODE_SECRET_SIZE + MD5_SIZE, length-CONFIG_NODE_SECRET_SIZE - MD5_SIZE); - ota.written += CONFIG_NODE_SECRET_SIZE + MD5_SIZE; - - mcu_led->blink(1, 1); - PRINTF("mqtt/ota: updating %u/%u\n", ota.written, Update.size()); - - } else { - if (!Update.isRunning()) { - PRINTLN("mqtt/ota: update is not running"); - return; - } - - if (index == ota.written) { - size_t written; - if ((written = Update.write(const_cast(payload), length)) != length) { - PRINTF("mqtt/ota: error: tried to write %ul bytes, write() returned %ul\n", - length, written); - ota.clean(); - Update.end(); - Update.clearError(); - sendOtaResponse(OTAResult::WRITE_ERROR); - return; - } - ota.written += length; - - mcu_led->blink(1, 1); - PRINTF("mqtt/ota: updating %u/%u\n", - ota.written - CONFIG_NODE_SECRET_SIZE - MD5_SIZE, - Update.size()); - } else { - PRINTF("mqtt/ota: position is invalid, expected %ul, got %ul\n", ota.written, index); - ota.clean(); - Update.end(); - Update.clearError(); - } - } - - if (Update.isFinished()) { - ota.dataPacketId = 0; - - if (Update.end()) { - ota.finished = true; - ota.publishResultPacketId = sendOtaResponse(OTAResult::OK); - PRINTF("mqtt/ota: ok, otares packet_id=%d\n", ota.publishResultPacketId); - } else { - ota.clean(); - - PRINTF("mqtt/ota: error: %u\n", Update.getError()); -#ifdef DEBUG - Update.printError(Serial); -#endif - Update.clearError(); - - sendOtaResponse(OTAResult::UPDATE_ERROR, Update.getError()); - } - } -} - -} \ No newline at end of file diff --git a/platformio/temphum/src/mqtt.h b/platformio/temphum/src/mqtt.h deleted file mode 100644 index 84df5b1..0000000 --- a/platformio/temphum/src/mqtt.h +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef HOMEKIT_TEMPHUM_MQTT_H -#define HOMEKIT_TEMPHUM_MQTT_H - -#include -#include -#include - -#include -#include - -namespace homekit { namespace mqtt { - -enum class OTAResult: uint8_t { - OK = 0, - UPDATE_ERROR = 1, - WRITE_ERROR = 2, -}; - -struct OTAStatus { - uint16_t dataPacketId; - uint16_t publishResultPacketId; - bool finished; - bool readyToRestart; - size_t written; - - OTAStatus() - : dataPacketId(0) - , publishResultPacketId(0) - , finished(false) - , readyToRestart(false) - , written(0) - {} - - inline void clean() { - dataPacketId = 0; - publishResultPacketId = 0; - finished = false; - readyToRestart = false; - written = 0; - } - - inline bool started() const { - return dataPacketId != 0; - } -}; - -class MQTT { -private: - String homeId; - WiFiClientSecure httpsSecureClient; - espMqttClientSecure client; - Ticker reconnectTimer; - Ticker restartTimer; - - void handleAdminOtaPayload(uint16_t packetId, const uint8_t* payload, size_t length, size_t index, size_t total); - - uint16_t publish(const String& topic, uint8_t* payload, size_t length); - uint16_t subscribe(const String& topic, uint8_t qos = 0); - - void sendInitialDiagnostics(); - uint16_t sendOtaResponse(OTAResult status, uint8_t error_code = 0); - -public: - StopWatch diagnosticsStopWatch; - OTAStatus ota; - - MQTT(); - void connect(); - void disconnect(); - void reconnect(); - void loop(); - void sendDiagnostics(); - void sendTempHumData(double temp, double rh); -}; - -struct DiagnosticsFlags { - uint8_t state: 1; - uint8_t config_changed_value_present: 1; - uint8_t config_changed: 1; - uint8_t reserved: 5; -} __attribute__((packed)); - -struct InitialDiagnosticsPayload { - uint32_t ip; - uint8_t fw_version; - int8_t rssi; - uint32_t free_heap; - DiagnosticsFlags flags; -} __attribute__((packed)); - -struct DiagnosticsPayload { - int8_t rssi; - uint32_t free_heap; - DiagnosticsFlags flags; -} __attribute__((packed)); - -struct PowerPayload { - char secret[12]; - uint8_t state; -} __attribute__((packed)); - -struct TempHumDataPayload { - double temp; - double rh; -} __attribute__((packed)); - -struct OTAResponse { - OTAResult status; - uint8_t error_code; -} __attribute__((packed)); - -} } - -#endif //HOMEKIT_TEMPHUM_MQTT_H \ No newline at end of file diff --git a/platformio/temphum/src/temphum.cpp b/platformio/temphum/src/temphum.cpp deleted file mode 100644 index 164f01e..0000000 --- a/platformio/temphum/src/temphum.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef CONFIG_TARGET_ESP01 -#include -#endif -#include -#include "temphum.h" - -namespace homekit::temphum { - -void BaseSensor::setup() const { -#ifndef CONFIG_TARGET_ESP01 - pinMode(CONFIG_SDA_GPIO, OUTPUT); - pinMode(CONFIG_SCL_GPIO, OUTPUT); - - Wire.begin(CONFIG_SDA_GPIO, CONFIG_SCL_GPIO); -#else - Wire.begin(); -#endif -} - -void BaseSensor::writeCommand(int reg) const { - Wire.beginTransmission(dev_addr); - Wire.write(reg); - Wire.endTransmission(); - delay(500); // wait for the measurement to be ready -} - -SensorData Si7021::read() { - writeCommand(0xf3); // command to measure temperature - Wire.requestFrom(dev_addr, 2); - uint16_t temp_raw = Wire.read() << 8 | Wire.read(); - double temperature = ((175.72 * temp_raw) / 65536.0) - 46.85; - - writeCommand(0xf5); // command to measure humidity - Wire.requestFrom(dev_addr, 2); - uint16_t hum_raw = Wire.read() << 8 | Wire.read(); - double humidity = ((125.0 * hum_raw) / 65536.0) - 6.0; - - return { - .temp = temperature, - .rh = humidity - }; -} - -SensorData DHT12::read() { - SensorData sd; - byte raw[5]; - - writeCommand(0); - Wire.requestFrom(dev_addr, 5); - - if (Wire.available() < 5) { - PRINTLN("DHT12: could not read 5 bytes"); - goto end; - } - - // Parse the received data - for (uint8_t i = 0; i < 5; i++) - raw[i] = Wire.read(); - - if (((raw[0] + raw[1] + raw[2] + raw[3]) & 0xff) != raw[4]) { - PRINTLN("DHT12: checksum error"); - goto end; - } - - // Calculate temperature and humidity values - sd.temp = raw[2] + (raw[3] & 0x7f) * 0.1; - if (raw[3] & 0x80) - sd.temp *= -1; - - sd.rh = raw[0] + raw[1] * 0.1; - -end: - return sd; -} - -} \ No newline at end of file diff --git a/platformio/temphum/src/temphum.h b/platformio/temphum/src/temphum.h deleted file mode 100644 index 3d8f373..0000000 --- a/platformio/temphum/src/temphum.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include - -namespace homekit::temphum { - -struct SensorData { - double temp = 0; // celsius - double rh = 0; // relative humidity percentage -}; - - -class BaseSensor { -protected: - int dev_addr; -public: - explicit BaseSensor(int dev) : dev_addr(dev) {} - void setup() const; - void writeCommand(int reg) const; - virtual SensorData read() = 0; -}; - - -class Si7021 : public BaseSensor { -public: - SensorData read() override; - Si7021() : BaseSensor(0x40) {} -}; - - -class DHT12 : public BaseSensor { -public: - SensorData read() override; - DHT12() : BaseSensor(0x5c) {} -}; - -} \ No newline at end of file -- cgit v1.2.3