platformio: make relay a library
This commit is contained in:
parent
0e021d0f1e
commit
d1435e2b1a
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homekit_mqtt",
|
||||
"version": "1.0.8",
|
||||
"version": "1.0.9",
|
||||
"build": {
|
||||
"flags": "-I../../include"
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
11
platformio/common/libs/mqtt_module_relay/library.json
Normal file
11
platformio/common/libs/mqtt_module_relay/library.json
Normal 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"
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
13
platformio/common/libs/relay/homekit/relay.h
Normal file
13
platformio/common/libs/relay/homekit/relay.h
Normal 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
|
8
platformio/common/libs/relay/library.json
Normal file
8
platformio/common/libs/relay/library.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "homekit_relay",
|
||||
"version": "1.0.0",
|
||||
"build": {
|
||||
"flags": "-I../../include"
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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
|
@ -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);
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user