diff options
Diffstat (limited to 'platformio/relayctl')
-rw-r--r-- | platformio/relayctl/src/leds.cpp | 11 | ||||
-rw-r--r-- | platformio/relayctl/src/leds.h | 15 | ||||
-rw-r--r-- | platformio/relayctl/src/main.cpp | 181 | ||||
-rw-r--r-- | platformio/relayctl/src/mqtt.cpp | 345 | ||||
-rw-r--r-- | platformio/relayctl/src/mqtt.h | 107 | ||||
-rw-r--r-- | platformio/relayctl/src/relay.h | 26 |
6 files changed, 20 insertions, 665 deletions
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 diff --git a/platformio/relayctl/src/relay.h b/platformio/relayctl/src/relay.h deleted file mode 100644 index 5287f09..0000000 --- a/platformio/relayctl/src/relay.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef HOMEKIT_RELAYCTL_RELAY_H -#define HOMEKIT_RELAYCTL_RELAY_H - -#include <Arduino.h> - -namespace homekit::relay { - -inline void init() { - pinMode(CONFIG_RELAY_GPIO, OUTPUT); -} - -inline bool getState() { - return digitalRead(CONFIG_RELAY_GPIO) == HIGH; -} - -inline void setOn() { - digitalWrite(CONFIG_RELAY_GPIO, HIGH); -} - -inline void setOff() { - digitalWrite(CONFIG_RELAY_GPIO, LOW); -} - -} - -#endif //HOMEKIT_RELAYCTL_RELAY_H
\ No newline at end of file |