platformio: make relay a library

This commit is contained in:
Evgeny Zinoviev 2023-05-30 01:01:09 +03:00
parent 0e021d0f1e
commit d1435e2b1a
20 changed files with 157 additions and 670 deletions

View File

@ -39,9 +39,13 @@ public:
initialized = true;
}
inline short getTickInterval() {
return tickInterval;
}
friend class Mqtt;
};
}
#endif //HOMEKIT_LIB_MQTT_MODULE_H
#endif //HOMEKIT_LIB_MQTT_MODULE_H

View File

@ -25,7 +25,7 @@ using namespace espMqttClientTypes;
Mqtt::Mqtt() {
auto cfg = config::read();
homeId = String(cfg.flags.node_configured ? cfg.node_id : wifi::NODE_ID);
nodeId = String(cfg.flags.node_configured ? cfg.node_id : wifi::NODE_ID);
randomSeed(micros());
@ -79,7 +79,7 @@ Mqtt::Mqtt() {
PRINTF("mqtt: message received, topic=%s, qos=%d, dup=%d, retain=%d, len=%ul, index=%ul, total=%ul\n",
topic, properties.qos, (int)properties.dup, (int)properties.retain, len, index, total);
const char *ptr = topic + homeId.length() + 10;
const char *ptr = topic + nodeId.length() + 10;
String relevantTopic(ptr);
auto it = moduleSubscriptions.find(relevantTopic);
@ -130,22 +130,24 @@ void Mqtt::disconnect() {
void Mqtt::loop() {
client.loop();
for (auto& module: modules) {
module->tick(*this);
if (module->getTickInterval() != 0)
module->tick(*this);
}
}
uint16_t Mqtt::publish(const String& topic, uint8_t* payload, size_t length) {
String fullTopic = "hk/" + homeId + "/temphum/" + topic;
String fullTopic = "hk/" + nodeId + "/" + topic;
return client.publish(fullTopic.c_str(), 1, false, payload, length);
}
uint16_t Mqtt::subscribe(const String& topic, uint8_t qos) {
String fullTopic = "hk/" + homeId + "/temphum/" + topic;
String fullTopic = "hk/" + nodeId + "/" + topic;
PRINTF("mqtt: subscribing to %s...\n", fullTopic.c_str());
uint16_t packetId = client.subscribe(fullTopic.c_str(), qos);
if (!packetId)
PRINTF("error: failed to subscribe to %s\n", fullTopic.c_str());
return packetId;
}
@ -157,8 +159,9 @@ void Mqtt::addModule(MqttModule* module) {
}
}
void Mqtt::subscribeModule(String& topic, MqttModule* module) {
void Mqtt::subscribeModule(String& topic, MqttModule* module, uint8_t qos) {
moduleSubscriptions[topic] = module;
subscribe(topic, qos);
}
}

View File

@ -22,7 +22,7 @@ class MqttModule;
class Mqtt {
private:
String homeId;
String nodeId;
WiFiClientSecure httpsSecureClient;
espMqttClientSecure client;
Ticker reconnectTimer;
@ -39,10 +39,10 @@ public:
void reconnect();
void loop();
void addModule(MqttModule* module);
void subscribeModule(String& topic, MqttModule* module);
void subscribeModule(String& topic, MqttModule* module, uint8_t qos = 0);
uint16_t publish(const String& topic, uint8_t* payload, size_t length);
};
}
#endif //HOMEKIT_LIB_MQTT_H
#endif //HOMEKIT_LIB_MQTT_H

View File

@ -1,6 +1,6 @@
{
"name": "homekit_mqtt",
"version": "1.0.8",
"version": "1.0.9",
"build": {
"flags": "-I../../include"
}

View File

@ -4,8 +4,8 @@
namespace homekit::mqtt {
static const char TOPIC_DIAGNOSTICS[] = "stat";
static const char TOPIC_INITIAL_DIAGNOSTICS[] = "stat1";
static const char TOPIC_DIAGNOSTICS[] = "diag";
static const char TOPIC_INITIAL_DIAGNOSTICS[] = "d1ag";
void MqttDiagnosticsModule::init(Mqtt& mqtt) {}
@ -44,4 +44,4 @@ void MqttDiagnosticsModule::tick(Mqtt& mqtt) {
}
}
}
}

View File

@ -1,7 +1,10 @@
{
"name": "homekit_mqtt_module_diagnostics",
"version": "1.0.0",
"version": "1.0.1",
"build": {
"flags": "-I../../include"
},
"dependencies": {
"homekit_mqtt": "file://../common/libs/mqtt"
}
}

View File

@ -1,10 +1,11 @@
{
"name": "homekit_mqtt_module_ota",
"version": "1.0.1",
"version": "1.0.2",
"build": {
"flags": "-I../../include"
},
"dependencies": {
"homekit_led": "file://../common/libs/led"
"homekit_led": "file://../common/libs/led",
"homekit_mqtt": "file://../common/libs/mqtt"
}
}

View File

@ -0,0 +1,43 @@
#include "./relay.h"
#include <homekit/relay.h>
#include <homekit/logging.h>
namespace homekit::mqtt {
static const char TOPIC_RELAY_SWITCH[] = "relay/switch";
void MqttRelayModule::init(Mqtt &mqtt) {
String topic(TOPIC_RELAY_SWITCH);
mqtt.subscribeModule(topic, this, 1);
}
void MqttRelayModule::tick(homekit::mqtt::Mqtt& mqtt) {}
void MqttRelayModule::handlePayload(Mqtt& mqtt, String& topic, uint16_t packetId, const uint8_t *payload, size_t length, size_t index, size_t total) {
if (topic != TOPIC_RELAY_SWITCH)
return;
if (length != sizeof(MqttRelaySwitchPayload)) {
PRINTF("error: size of payload (%ul) does not match expected (%ul)\n",
length, sizeof(MqttRelaySwitchPayload));
return;
}
auto pd = reinterpret_cast<const struct MqttRelaySwitchPayload*>(payload);
if (strncmp(pd->secret, MQTT_SECRET, sizeof(pd->secret)) != 0) {
PRINTLN("error: invalid secret");
return;
}
if (pd->state == 1) {
PRINTLN("mqtt: turning relay on");
relay::on();
} else if (pd->state == 0) {
PRINTLN("mqtt: turning relay off");
relay::off();
} else {
PRINTLN("error: unexpected state value");
}
}
}

View File

@ -0,0 +1,23 @@
#ifndef HOMEKIT_LIB_MQTT_MODULE_RELAY_H
#define HOMEKIT_LIB_MQTT_MODULE_RELAY_H
#include <homekit/mqtt/module.h>
namespace homekit::mqtt {
struct MqttRelaySwitchPayload {
char secret[12];
uint8_t state;
} __attribute__((packed));
class MqttRelayModule : public MqttModule {
public:
MqttRelayModule() : MqttModule(0) {}
void init(Mqtt& mqtt) override;
void tick(Mqtt& mqtt) override;
void handlePayload(Mqtt& mqtt, String& topic, uint16_t packetId, const uint8_t *payload, size_t length, size_t index, size_t total) override;
};
}
#endif //HOMEKIT_LIB_MQTT_MODULE_RELAY_H

View File

@ -0,0 +1,11 @@
{
"name": "homekit_mqtt_module_relay",
"version": "1.0.3",
"build": {
"flags": "-I../../include"
},
"dependencies": {
"homekit_mqtt": "file://../common/libs/mqtt",
"homekit_relay": "file://../common/libs/relay"
}
}

View File

@ -2,7 +2,7 @@
namespace homekit::mqtt {
static const char TOPIC_TEMPHUM_DATA[] = "data";
static const char TOPIC_TEMPHUM_DATA[] = "temphum/data";
void MqttTemphumModule::init(Mqtt &mqtt) {}
@ -20,4 +20,4 @@ void MqttTemphumModule::tick(homekit::mqtt::Mqtt& mqtt) {
mqtt.publish(TOPIC_TEMPHUM_DATA, reinterpret_cast<uint8_t*>(&payload), sizeof(payload));
}
}
}

View File

@ -1,10 +1,11 @@
{
"name": "homekit_mqtt_module_temphum",
"version": "1.0.8",
"version": "1.0.9",
"build": {
"flags": "-I../../include"
},
"dependencies": {
"homekit_mqtt": "file://../common/libs/mqtt",
"homekit_temphum": "file://../common/libs/temphum"
}
}

View File

@ -1,26 +1,22 @@
#ifndef HOMEKIT_RELAYCTL_RELAY_H
#define HOMEKIT_RELAYCTL_RELAY_H
#include <Arduino.h>
#include "./relay.h"
namespace homekit::relay {
inline void init() {
void init() {
pinMode(CONFIG_RELAY_GPIO, OUTPUT);
}
inline bool getState() {
bool state() {
return digitalRead(CONFIG_RELAY_GPIO) == HIGH;
}
inline void setOn() {
void on() {
digitalWrite(CONFIG_RELAY_GPIO, HIGH);
}
inline void setOff() {
void off() {
digitalWrite(CONFIG_RELAY_GPIO, LOW);
}
}
#endif //HOMEKIT_RELAYCTL_RELAY_H

View File

@ -0,0 +1,13 @@
#ifndef HOMEKIT_LIB_RELAY_H
#define HOMEKIT_LIB_RELAY_H
namespace homekit::relay {
void init();
bool state();
void on();
void off();
}
#endif //HOMEKIT_LIB_RELAY_H

View File

@ -0,0 +1,8 @@
{
"name": "homekit_relay",
"version": "1.0.0",
"build": {
"flags": "-I../../include"
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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
MqttRelayModule* mqttRelayModule = nullptr;
static void onMqttCreated(Mqtt& mqtt);
LoopConfig loopConfig = {
.onMqttCreated = onMqttCreated
};
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);
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..");
}
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();
}
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;
}
main::loop(&loopConfig);
}
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();
static void onMqttCreated(Mqtt& mqtt) {
if (mqttRelayModule == nullptr) {
mqttRelayModule = new MqttRelayModule();
mqtt.addModule(mqttRelayModule);
}
}
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);
}

View File

@ -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());
}
}
}
}

View File

@ -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