diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2023-05-29 05:44:59 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2023-05-29 16:31:08 +0300 |
commit | 8b2088103a74e616ca82fe043de55cb7ed58e329 (patch) | |
tree | f8dad8b7bbe9cb6eb135e63cb3ece2338e035aba /platformio/common | |
parent | 6a64c97c799bd4919b46f8dc39d80799bb751ab3 (diff) |
platformio: split code into libraries
Diffstat (limited to 'platformio/common')
26 files changed, 1111 insertions, 44 deletions
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/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 <Arduino.h> +#endif +#include <homekit/logging.h> +#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 <Wire.h> + +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" + } +} + |