diff options
20 files changed, 156 insertions, 669 deletions
diff --git a/platformio/common/libs/mqtt/homekit/mqtt/module.h b/platformio/common/libs/mqtt/homekit/mqtt/module.h index 7bf2522..e4a01f8 100644 --- a/platformio/common/libs/mqtt/homekit/mqtt/module.h +++ b/platformio/common/libs/mqtt/homekit/mqtt/module.h @@ -39,9 +39,13 @@ public: initialized = true; } + inline short getTickInterval() { + return tickInterval; + } + friend class Mqtt; }; } -#endif //HOMEKIT_LIB_MQTT_MODULE_H
\ No newline at end of file +#endif //HOMEKIT_LIB_MQTT_MODULE_H diff --git a/platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp index 9d96f9f..cb2cea7 100644 --- a/platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp +++ b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp @@ -25,7 +25,7 @@ using namespace espMqttClientTypes; Mqtt::Mqtt() { auto cfg = config::read(); - homeId = String(cfg.flags.node_configured ? cfg.node_id : wifi::NODE_ID); + nodeId = String(cfg.flags.node_configured ? cfg.node_id : wifi::NODE_ID); randomSeed(micros()); @@ -79,7 +79,7 @@ Mqtt::Mqtt() { 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; + const char *ptr = topic + nodeId.length() + 10; String relevantTopic(ptr); auto it = moduleSubscriptions.find(relevantTopic); @@ -130,22 +130,24 @@ void Mqtt::disconnect() { void Mqtt::loop() { client.loop(); for (auto& module: modules) { - module->tick(*this); + if (module->getTickInterval() != 0) + module->tick(*this); } } uint16_t Mqtt::publish(const String& topic, uint8_t* payload, size_t length) { - String fullTopic = "hk/" + homeId + "/temphum/" + topic; + String fullTopic = "hk/" + nodeId + "/" + 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; + String fullTopic = "hk/" + nodeId + "/" + 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; } @@ -157,8 +159,9 @@ void Mqtt::addModule(MqttModule* module) { } } -void Mqtt::subscribeModule(String& topic, MqttModule* module) { +void Mqtt::subscribeModule(String& topic, MqttModule* module, uint8_t qos) { moduleSubscriptions[topic] = module; + subscribe(topic, qos); } } diff --git a/platformio/common/libs/mqtt/homekit/mqtt/mqtt.h b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.h index 983cc5a..9e0c2be 100644 --- a/platformio/common/libs/mqtt/homekit/mqtt/mqtt.h +++ b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.h @@ -22,7 +22,7 @@ class MqttModule; class Mqtt { private: - String homeId; + String nodeId; WiFiClientSecure httpsSecureClient; espMqttClientSecure client; Ticker reconnectTimer; @@ -39,10 +39,10 @@ public: void reconnect(); void loop(); void addModule(MqttModule* module); - void subscribeModule(String& topic, MqttModule* module); + void subscribeModule(String& topic, MqttModule* module, uint8_t qos = 0); uint16_t publish(const String& topic, uint8_t* payload, size_t length); }; } -#endif //HOMEKIT_LIB_MQTT_H
\ No newline at end of file +#endif //HOMEKIT_LIB_MQTT_H diff --git a/platformio/common/libs/mqtt/library.json b/platformio/common/libs/mqtt/library.json index 179b10a..d1ad420 100644 --- a/platformio/common/libs/mqtt/library.json +++ b/platformio/common/libs/mqtt/library.json @@ -1,6 +1,6 @@ { "name": "homekit_mqtt", - "version": "1.0.8", + "version": "1.0.9", "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 index f022f79..d36a7e9 100644 --- a/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.cpp +++ b/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.cpp @@ -4,8 +4,8 @@ namespace homekit::mqtt { -static const char TOPIC_DIAGNOSTICS[] = "stat"; -static const char TOPIC_INITIAL_DIAGNOSTICS[] = "stat1"; +static const char TOPIC_DIAGNOSTICS[] = "diag"; +static const char TOPIC_INITIAL_DIAGNOSTICS[] = "d1ag"; void MqttDiagnosticsModule::init(Mqtt& mqtt) {} @@ -44,4 +44,4 @@ void MqttDiagnosticsModule::tick(Mqtt& mqtt) { } } -}
\ No newline at end of file +} diff --git a/platformio/common/libs/mqtt_module_diagnostics/library.json b/platformio/common/libs/mqtt_module_diagnostics/library.json index 81bf0fb..8df306d 100644 --- a/platformio/common/libs/mqtt_module_diagnostics/library.json +++ b/platformio/common/libs/mqtt_module_diagnostics/library.json @@ -1,7 +1,10 @@ { "name": "homekit_mqtt_module_diagnostics", - "version": "1.0.0", + "version": "1.0.1", "build": { "flags": "-I../../include" + }, + "dependencies": { + "homekit_mqtt": "file://../common/libs/mqtt" } } diff --git a/platformio/common/libs/mqtt_module_ota/library.json b/platformio/common/libs/mqtt_module_ota/library.json index 1eeda1b..30db7d2 100644 --- a/platformio/common/libs/mqtt_module_ota/library.json +++ b/platformio/common/libs/mqtt_module_ota/library.json @@ -1,10 +1,11 @@ { "name": "homekit_mqtt_module_ota", - "version": "1.0.1", + "version": "1.0.2", "build": { "flags": "-I../../include" }, "dependencies": { - "homekit_led": "file://../common/libs/led" + "homekit_led": "file://../common/libs/led", + "homekit_mqtt": "file://../common/libs/mqtt" } } diff --git a/platformio/common/libs/mqtt_module_relay/homekit/mqtt/module/relay.cpp b/platformio/common/libs/mqtt_module_relay/homekit/mqtt/module/relay.cpp new file mode 100644 index 0000000..ab40727 --- /dev/null +++ b/platformio/common/libs/mqtt_module_relay/homekit/mqtt/module/relay.cpp @@ -0,0 +1,43 @@ +#include "./relay.h" +#include <homekit/relay.h> +#include <homekit/logging.h> + +namespace homekit::mqtt { + +static const char TOPIC_RELAY_SWITCH[] = "relay/switch"; + +void MqttRelayModule::init(Mqtt &mqtt) { + String topic(TOPIC_RELAY_SWITCH); + mqtt.subscribeModule(topic, this, 1); +} + +void MqttRelayModule::tick(homekit::mqtt::Mqtt& mqtt) {} + +void MqttRelayModule::handlePayload(Mqtt& mqtt, String& topic, uint16_t packetId, const uint8_t *payload, size_t length, size_t index, size_t total) { + if (topic != TOPIC_RELAY_SWITCH) + return; + + if (length != sizeof(MqttRelaySwitchPayload)) { + PRINTF("error: size of payload (%ul) does not match expected (%ul)\n", + length, sizeof(MqttRelaySwitchPayload)); + return; + } + + auto pd = reinterpret_cast<const struct MqttRelaySwitchPayload*>(payload); + if (strncmp(pd->secret, MQTT_SECRET, sizeof(pd->secret)) != 0) { + PRINTLN("error: invalid secret"); + return; + } + + if (pd->state == 1) { + PRINTLN("mqtt: turning relay on"); + relay::on(); + } else if (pd->state == 0) { + PRINTLN("mqtt: turning relay off"); + relay::off(); + } else { + PRINTLN("error: unexpected state value"); + } +} + +} diff --git a/platformio/common/libs/mqtt_module_relay/homekit/mqtt/module/relay.h b/platformio/common/libs/mqtt_module_relay/homekit/mqtt/module/relay.h new file mode 100644 index 0000000..6420de1 --- /dev/null +++ b/platformio/common/libs/mqtt_module_relay/homekit/mqtt/module/relay.h @@ -0,0 +1,23 @@ +#ifndef HOMEKIT_LIB_MQTT_MODULE_RELAY_H +#define HOMEKIT_LIB_MQTT_MODULE_RELAY_H + +#include <homekit/mqtt/module.h> + +namespace homekit::mqtt { + +struct MqttRelaySwitchPayload { + char secret[12]; + uint8_t state; +} __attribute__((packed)); + +class MqttRelayModule : public MqttModule { +public: + MqttRelayModule() : MqttModule(0) {} + 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; +}; + +} + +#endif //HOMEKIT_LIB_MQTT_MODULE_RELAY_H diff --git a/platformio/common/libs/mqtt_module_relay/library.json b/platformio/common/libs/mqtt_module_relay/library.json new file mode 100644 index 0000000..e71cf95 --- /dev/null +++ b/platformio/common/libs/mqtt_module_relay/library.json @@ -0,0 +1,11 @@ +{ + "name": "homekit_mqtt_module_relay", + "version": "1.0.3", + "build": { + "flags": "-I../../include" + }, + "dependencies": { + "homekit_mqtt": "file://../common/libs/mqtt", + "homekit_relay": "file://../common/libs/relay" + } +} 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 index 8b7a2ef..82f1d74 100644 --- a/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.cpp +++ b/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.cpp @@ -2,7 +2,7 @@ namespace homekit::mqtt { -static const char TOPIC_TEMPHUM_DATA[] = "data"; +static const char TOPIC_TEMPHUM_DATA[] = "temphum/data"; void MqttTemphumModule::init(Mqtt &mqtt) {} @@ -20,4 +20,4 @@ void MqttTemphumModule::tick(homekit::mqtt::Mqtt& mqtt) { mqtt.publish(TOPIC_TEMPHUM_DATA, reinterpret_cast<uint8_t*>(&payload), sizeof(payload)); } -}
\ No newline at end of file +} diff --git a/platformio/common/libs/mqtt_module_temphum/library.json b/platformio/common/libs/mqtt_module_temphum/library.json index 30f5eb6..9bb8cf1 100644 --- a/platformio/common/libs/mqtt_module_temphum/library.json +++ b/platformio/common/libs/mqtt_module_temphum/library.json @@ -1,10 +1,11 @@ { "name": "homekit_mqtt_module_temphum", - "version": "1.0.8", + "version": "1.0.9", "build": { "flags": "-I../../include" }, "dependencies": { + "homekit_mqtt": "file://../common/libs/mqtt", "homekit_temphum": "file://../common/libs/temphum" } } diff --git a/platformio/relayctl/src/relay.h b/platformio/common/libs/relay/homekit/relay.cpp index 5287f09..b00a7a2 100644 --- a/platformio/relayctl/src/relay.h +++ b/platformio/common/libs/relay/homekit/relay.cpp @@ -1,26 +1,22 @@ -#ifndef HOMEKIT_RELAYCTL_RELAY_H -#define HOMEKIT_RELAYCTL_RELAY_H - #include <Arduino.h> +#include "./relay.h" namespace homekit::relay { - -inline void init() { + +void init() { pinMode(CONFIG_RELAY_GPIO, OUTPUT); } -inline bool getState() { +bool state() { return digitalRead(CONFIG_RELAY_GPIO) == HIGH; } -inline void setOn() { +void on() { digitalWrite(CONFIG_RELAY_GPIO, HIGH); } -inline void setOff() { +void off() { digitalWrite(CONFIG_RELAY_GPIO, LOW); } } - -#endif //HOMEKIT_RELAYCTL_RELAY_H
\ No newline at end of file diff --git a/platformio/common/libs/relay/homekit/relay.h b/platformio/common/libs/relay/homekit/relay.h new file mode 100644 index 0000000..288cc05 --- /dev/null +++ b/platformio/common/libs/relay/homekit/relay.h @@ -0,0 +1,13 @@ +#ifndef HOMEKIT_LIB_RELAY_H +#define HOMEKIT_LIB_RELAY_H + +namespace homekit::relay { + +void init(); +bool state(); +void on(); +void off(); + +} + +#endif //HOMEKIT_LIB_RELAY_H diff --git a/platformio/common/libs/relay/library.json b/platformio/common/libs/relay/library.json new file mode 100644 index 0000000..e878248 --- /dev/null +++ b/platformio/common/libs/relay/library.json @@ -0,0 +1,8 @@ +{ + "name": "homekit_relay", + "version": "1.0.0", + "build": { + "flags": "-I../../include" + } +} + diff --git a/platformio/relayctl/src/leds.cpp b/platformio/relayctl/src/leds.cpp deleted file mode 100644 index f0c9d51..0000000 --- a/platformio/relayctl/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/relayctl/src/leds.h b/platformio/relayctl/src/leds.h deleted file mode 100644 index 04c1f79..0000000 --- a/platformio/relayctl/src/leds.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef HOMEKIT_TEMPHUM_LEDS_H -#define HOMEKIT_TEMPHUM_LEDS_H - -#include <homekit/led.h> - -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/relayctl/src/main.cpp b/platformio/relayctl/src/main.cpp index 13b00b3..c399641 100644 --- a/platformio/relayctl/src/main.cpp +++ b/platformio/relayctl/src/main.cpp @@ -1,176 +1,35 @@ #include <Arduino.h> -#include <ESP8266WiFi.h> -#include <DNSServer.h> -#include <Ticker.h> - -#include <homekit/config.h> -#include <homekit/logging.h> -#include <homekit/http_server.h> -#include <homekit/wifi.h> -#include <homekit/stopwatch.h> - -#include "relay.h" -#include "leds.h" -#include "mqtt.h" +#include <Wire.h> +#include <homekit/main.h> +#include <homekit/mqtt/mqtt.h> +#include <homekit/mqtt/module/relay.h> +#include <homekit/relay.h> using namespace homekit; +using main::LoopConfig; +using mqtt::Mqtt; +using mqtt::MqttRelayModule; -enum class WorkingMode { - RECOVERY, // AP mode, http server with configuration - NORMAL, // MQTT client -}; -static enum WorkingMode working_mode = WorkingMode::NORMAL; - -enum class WiFiConnectionState { - WAITING = 0, - JUST_CONNECTED = 1, - CONNECTED = 2 -}; - -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; -#if MQTT_BLINK -static StopWatch blinkStopWatch; -#endif - -static DNSServer* dnsServer = nullptr; - -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); +MqttRelayModule* mqttRelayModule = nullptr; - PRINTF("Wi-Fi STA creds: ssid=%s, psk=%s, hostname=%s\n", ssid, psk, hostname); +static void onMqttCreated(Mqtt& mqtt); - wifi_state = WiFiConnectionState::WAITING; - - WiFi.mode(WIFI_STA); - WiFi.hostname(hostname); - WiFi.begin(ssid, psk); - - PRINT("connecting to wifi.."); -} - -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(); -} +LoopConfig loopConfig = { + .onMqttCreated = onMqttCreated +}; void setup() { - WiFi.disconnect(); - -#ifdef DEBUG - Serial.begin(115200); -#endif - + main::setup(); relay::init(); - - 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; - } - } - - auto cfg = config::read(); - if (config::isDirty(cfg)) { - PRINTLN("config is dirty, erasing..."); - config::erase(cfg); - board_led->blink(10, 50); - } - - switch (working_mode) { - case WorkingMode::RECOVERY: - wifiHotspot(); - break; - - case WorkingMode::NORMAL: - wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnected); - wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnected); - wifiConnect(); - break; - } } void loop() { - if (working_mode == WorkingMode::NORMAL) { - if (wifi_state == WiFiConnectionState::WAITING) { - PRINT("."); - mcu_led->blink(2, 50); - delay(1000); - return; - } - - if (wifi_state == WiFiConnectionState::JUST_CONNECTED) { - board_led->blink(3, 300); - 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<int>(wifi_state) >= 1 && mqtt != nullptr) { - mqtt->loop(); - - if (mqtt->ota.readyToRestart) { - mqtt->disconnect(); - } else if (mqtt->diagnosticsStopWatch.elapsed(10000)) { - mqtt->sendDiagnostics(); - } - -#if MQTT_BLINK - // periodically blink board led - if (blinkStopWatch.elapsed(5000)) { - board_led->blink(1, 10); - blinkStopWatch.save(); - } -#endif - } - } else { - if (dnsServer != nullptr) - dnsServer->processNextRequest(); - - auto httpServer = (HttpServer*)service; - if (httpServer != nullptr) - httpServer->loop(); - } + 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 (mqttRelayModule == nullptr) { + mqttRelayModule = new MqttRelayModule(); + mqtt.addModule(mqttRelayModule); + } }
\ No newline at end of file diff --git a/platformio/relayctl/src/mqtt.cpp b/platformio/relayctl/src/mqtt.cpp deleted file mode 100644 index ad3caef..0000000 --- a/platformio/relayctl/src/mqtt.cpp +++ /dev/null @@ -1,345 +0,0 @@ -#include <ESP8266httpUpdate.h> - -#include <homekit/logging.h> -#include <homekit/wifi.h> -#include <homekit/util.h> -#include <homekit/mqtt.h> - -#include "relay.h" -#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_RELAY_POWER[] = "power"; -static const char TOPIC_ADMIN_OTA[] = "admin/ota"; -static const uint16_t MQTT_KEEPALIVE = 30; - -enum class IncomingMessage { - UNKNOWN, - RELAY_POWER, - 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_RELAY_POWER, 1); - subscribe(TOPIC_ADMIN_OTA); - }); - - client.onDisconnect([&](DisconnectReason reason) { - PRINTF("mqtt: disconnected, reason=%d\n", static_cast<int>(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<unsigned int>(*(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_RELAY_POWER) - msgType = IncomingMessage::RELAY_POWER; - else 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::RELAY_POWER: - handleRelayPowerPayload(payload, total); - break; - - 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 + "/relay/" + 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 + "/relay/" + 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 = static_cast<uint8_t>(relay::getState() ? 1 : 0), - .config_changed_value_present = 1, - .config_changed = static_cast<uint8_t>(cfg.flags.node_configured || - cfg.flags.wifi_configured ? 1 : 0) - } - }; - publish(TOPIC_INITIAL_DIAGNOSTICS, reinterpret_cast<uint8_t*>(&stat), sizeof(stat)); - diagnosticsStopWatch.save(); -} - -void MQTT::sendDiagnostics() { - DiagnosticsPayload stat{ - .rssi = wifi::getRSSI(), - .free_heap = ESP.getFreeHeap(), - .flags = DiagnosticsFlags{ - .state = static_cast<uint8_t>(relay::getState() ? 1 : 0), - .config_changed_value_present = 0, - .config_changed = 0 - } - }; - publish(TOPIC_DIAGNOSTICS, reinterpret_cast<uint8_t*>(&stat), sizeof(stat)); - diagnosticsStopWatch.save(); -} - -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<uint8_t*>(&resp), sizeof(resp)); -} - -void MQTT::handleRelayPowerPayload(const uint8_t *payload, uint32_t length) { - if (length != sizeof(PowerPayload)) { - PRINTF("error: size of payload (%ul) does not match expected (%ul)\n", - length, sizeof(PowerPayload)); - return; - } - - auto pd = reinterpret_cast<const struct PowerPayload*>(payload); - if (strncmp(pd->secret, MQTT_SECRET, sizeof(pd->secret)) != 0) { - PRINTLN("error: invalid secret"); - return; - } - - if (pd->state == 1) { - PRINTLN("mqtt: turning relay on"); - relay::setOn(); - } else if (pd->state == 0) { - PRINTLN("mqtt: turning relay off"); - relay::setOff(); - } else { - PRINTLN("error: unexpected state value"); - } - - sendDiagnostics(); -} - -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-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<uint8_t*>(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<uint8_t*>(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/relayctl/src/mqtt.h b/platformio/relayctl/src/mqtt.h deleted file mode 100644 index 74d637a..0000000 --- a/platformio/relayctl/src/mqtt.h +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef HOMEKIT_RELAYCTL_MQTT_H -#define HOMEKIT_RELAYCTL_MQTT_H - -#include <ESP8266WiFi.h> -#include <espMqttClient.h> -#include <Ticker.h> - -#include <homekit/stopwatch.h> - -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; - } -}; - -class MQTT { -private: - String homeId; - WiFiClientSecure httpsSecureClient; - espMqttClientSecure client; - Ticker reconnectTimer; - Ticker restartTimer; - - void handleRelayPowerPayload(const uint8_t* payload, uint32_t length); - 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(); -}; - -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 OTAResponse { - OTAResult status; - uint8_t error_code; -} __attribute__((packed)); - -} - -#endif //HOMEKIT_RELAYCTL_MQTT_H
\ No newline at end of file |