diff options
36 files changed, 1111 insertions, 709 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..88ec6c0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,66 @@ +# Generated from CLion C/C++ Code Style settings +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: None +AlignOperands: Align +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Always +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +ColumnLimit: 0 +CompactNamespaces: false +ContinuationIndentWidth: 8 +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Left +ReflowComments: false +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 0 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never @@ -1,4 +1,5 @@ .idea +.vscode /venv /node_modules *.pyc 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 <Arduino.h> #include <stdint.h> -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 <homekit/led.h> +#include <homekit/mqtt/mqtt.h> +#include <homekit/mqtt/module/diagnostics.h> +#include <homekit/mqtt/module/ota.h> + +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<int>(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 <Arduino.h> +#include <ESP8266WiFi.h> +#include <DNSServer.h> +#include <Ticker.h> +#include <Wire.h> + +#include <homekit/config.h> +#include <homekit/logging.h> +#ifndef CONFIG_TARGET_ESP01 +#include <homekit/http_server.h> +#endif +#include <homekit/wifi.h> +#include <homekit/mqtt/mqtt.h> + +#include <functional> + +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<void(mqtt::Mqtt&)> 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 <stdint.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]; - -} - -#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 <homekit/logging.h> + +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 <homekit/stopwatch.h> + + +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 <homekit/config.h> +#include <homekit/wifi.h> +#include <homekit/logging.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; + +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<int>(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<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); + + 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 <vector> +#include <map> +#include <cstdint> +#include <espMqttClient.h> +#include <Ticker.h> +#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<MqttModule*> modules; + std::map<String, MqttModule*> 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 <unistd.h> + +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 <homekit/wifi.h> +#include <ESP8266WiFi.h> + +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<uint8_t>(cfg.flags.node_configured || + cfg.flags.wifi_configured ? 1 : 0) + } + }; + mqtt.publish(TOPIC_INITIAL_DIAGNOSTICS, reinterpret_cast<uint8_t*>(&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<uint8_t*>(&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 <stdint.h> +#include <homekit/mqtt/module.h> + +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 <homekit/logging.h> +#include <homekit/util.h> +#include <homekit/led.h> + +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<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(); + 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<uint8_t*>(&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 <stdint.h> +#include <Ticker.h> +#include <homekit/mqtt/module.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; + } +}; + +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<uint8_t*>(&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 <homekit/mqtt/module.h> +#include <homekit/temphum.h> + +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/temphum/src/temphum.cpp b/platformio/common/libs/temphum/homekit/temphum.cpp index 164f01e..ac4d371 100644 --- a/platformio/temphum/src/temphum.cpp +++ b/platformio/common/libs/temphum/homekit/temphum.cpp @@ -6,7 +6,7 @@ namespace homekit::temphum { -void BaseSensor::setup() const { +void Sensor::setup() const { #ifndef CONFIG_TARGET_ESP01 pinMode(CONFIG_SDA_GPIO, OUTPUT); pinMode(CONFIG_SCL_GPIO, OUTPUT); @@ -17,7 +17,7 @@ void BaseSensor::setup() const { #endif } -void BaseSensor::writeCommand(int reg) const { +void Sensor::writeCommand(int reg) const { Wire.beginTransmission(dev_addr); Wire.write(reg); Wire.endTransmission(); @@ -25,25 +25,34 @@ void BaseSensor::writeCommand(int reg) const { } 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 { - .temp = temperature, - .rh = humidity + .error = error, + .temp = temperature, + .rh = humidity }; } SensorData DHT12::read() { SensorData sd; byte raw[5]; + sd.error = 1; writeCommand(0); Wire.requestFrom(dev_addr, 5); @@ -69,6 +78,8 @@ SensorData DHT12::read() { sd.rh = raw[0] + raw[1] * 0.1; + sd.error = 0; + end: return sd; } diff --git a/platformio/temphum/src/temphum.h b/platformio/common/libs/temphum/homekit/temphum.h index 3d8f373..1952ce0 100644 --- a/platformio/temphum/src/temphum.h +++ b/platformio/common/libs/temphum/homekit/temphum.h @@ -5,33 +5,34 @@ namespace homekit::temphum { struct SensorData { + uint8_t error = 0; double temp = 0; // celsius double rh = 0; // relative humidity percentage }; -class BaseSensor { +class Sensor { protected: int dev_addr; public: - explicit BaseSensor(int dev) : dev_addr(dev) {} + explicit Sensor(int dev) : dev_addr(dev) {} void setup() const; void writeCommand(int reg) const; virtual SensorData read() = 0; }; -class Si7021 : public BaseSensor { +class Si7021 : public Sensor { public: SensorData read() override; - Si7021() : BaseSensor(0x40) {} + Si7021() : Sensor(0x40) {} }; -class DHT12 : public BaseSensor { +class DHT12 : public Sensor { public: SensorData read() override; - DHT12() : BaseSensor(0x5c) {} + 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 <Arduino.h> +#include <homekit/main.h> + +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 <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/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 <Arduino.h> -#include <ESP8266WiFi.h> -#include <DNSServer.h> -#include <Ticker.h> #include <Wire.h> - -#include <homekit/config.h> -#include <homekit/logging.h> -#ifndef CONFIG_TARGET_ESP01 -#include <homekit/http_server.h> -#endif -#include <homekit/wifi.h> - -#include "mqtt.h" -#include "leds.h" -#include "temphum.h" +#include <homekit/main.h> +#include <homekit/mqtt/mqtt.h> +#include <homekit/mqtt/module/temphum.h> +#include <homekit/temphum.h> 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<int>(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 <ESP8266httpUpdate.h> -#include <homekit/logging.h> -#include <homekit/config.h> -#include <homekit/util.h> -#include <homekit/wifi.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_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<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_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<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 = 1, - .config_changed_value_present = 0, - .config_changed = 0 - } - }; - publish(TOPIC_DIAGNOSTICS, reinterpret_cast<uint8_t*>(&stat), sizeof(stat)); - diagnosticsStopWatch.save(); -} - -void MQTT::sendTempHumData(double temp, double rh) { - TempHumDataPayload data { - .temp = temp, - .rh = rh - }; - publish(TOPIC_TEMPHUM_DATA, reinterpret_cast<uint8_t*>(&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<uint8_t*>(&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<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/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 <ESP8266WiFi.h> -#include <espMqttClient.h> -#include <Ticker.h> - -#include <homekit/stopwatch.h> -#include <homekit/mqtt.h> - -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/src/home/mqtt/payload/temphum.py b/src/home/mqtt/payload/temphum.py index 5b45ecb..c0b744e 100644 --- a/src/home/mqtt/payload/temphum.py +++ b/src/home/mqtt/payload/temphum.py @@ -4,7 +4,7 @@ two_digits_precision = lambda x: round(x, 2) class TempHumDataPayload(MqttPayload): - FORMAT = '=dd' + FORMAT = '=ddb' UNPACKER = { 'temp': two_digits_precision, 'rh': two_digits_precision @@ -12,3 +12,4 @@ class TempHumDataPayload(MqttPayload): temp: float rh: float + error: int |