platformio: split code into libraries
This commit is contained in:
parent
6a64c97c79
commit
8b2088103a
66
.clang-format
Normal file
66
.clang-format
Normal file
@ -0,0 +1,66 @@
|
||||
# Generated from CLion C/C++ Code Style settings
|
||||
BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignOperands: Align
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Always
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: BeforeColon
|
||||
ColumnLimit: 0
|
||||
CompactNamespaces: false
|
||||
ContinuationIndentWidth: 8
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MaxEmptyLinesToKeep: 2
|
||||
NamespaceIndentation: All
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PointerAlignment: Left
|
||||
ReflowComments: false
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 0
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
.idea
|
||||
.vscode
|
||||
/venv
|
||||
/node_modules
|
||||
*.pyc
|
||||
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homekit_led",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.8",
|
||||
"build": {
|
||||
"flags": "-I../../include"
|
||||
}
|
||||
|
194
platformio/common/libs/main/homekit/main.cpp
Normal file
194
platformio/common/libs/main/homekit/main.cpp
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
48
platformio/common/libs/main/homekit/main.h
Normal file
48
platformio/common/libs/main/homekit/main.h
Normal file
@ -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
|
12
platformio/common/libs/main/library.json
Normal file
12
platformio/common/libs/main/library.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -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
|
26
platformio/common/libs/mqtt/homekit/mqtt/module.cpp
Normal file
26
platformio/common/libs/mqtt/homekit/mqtt/module.cpp
Normal file
@ -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) {}
|
||||
|
||||
}
|
47
platformio/common/libs/mqtt/homekit/mqtt/module.h
Normal file
47
platformio/common/libs/mqtt/homekit/mqtt/module.h
Normal file
@ -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
|
164
platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp
Normal file
164
platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
48
platformio/common/libs/mqtt/homekit/mqtt/mqtt.h
Normal file
48
platformio/common/libs/mqtt/homekit/mqtt/mqtt.h
Normal file
@ -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
|
15
platformio/common/libs/mqtt/homekit/mqtt/payload.h
Normal file
15
platformio/common/libs/mqtt/homekit/mqtt/payload.h
Normal file
@ -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
|
@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "homekit_mqtt",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.8",
|
||||
"build": {
|
||||
"flags": "-I../../include"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "homekit_mqtt_module_diagnostics",
|
||||
"version": "1.0.0",
|
||||
"build": {
|
||||
"flags": "-I../../include"
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
10
platformio/common/libs/mqtt_module_ota/library.json
Normal file
10
platformio/common/libs/mqtt_module_ota/library.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "homekit_mqtt_module_ota",
|
||||
"version": "1.0.1",
|
||||
"build": {
|
||||
"flags": "-I../../include"
|
||||
},
|
||||
"dependencies": {
|
||||
"homekit_led": "file://../common/libs/led"
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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
|
10
platformio/common/libs/mqtt_module_temphum/library.json
Normal file
10
platformio/common/libs/mqtt_module_temphum/library.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "homekit_mqtt_module_temphum",
|
||||
"version": "1.0.8",
|
||||
"build": {
|
||||
"flags": "-I../../include"
|
||||
},
|
||||
"dependencies": {
|
||||
"homekit_temphum": "file://../common/libs/temphum"
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
|
||||
namespace homekit::temphum {
|
||||
|
||||
void BaseSensor::setup() const {
|
||||
void Sensor::setup() const {
|
||||
#ifndef CONFIG_TARGET_ESP01
|
||||
pinMode(CONFIG_SDA_GPIO, OUTPUT);
|
||||
pinMode(CONFIG_SCL_GPIO, OUTPUT);
|
||||
@ -17,7 +17,7 @@ void BaseSensor::setup() const {
|
||||
#endif
|
||||
}
|
||||
|
||||
void BaseSensor::writeCommand(int reg) const {
|
||||
void Sensor::writeCommand(int reg) const {
|
||||
Wire.beginTransmission(dev_addr);
|
||||
Wire.write(reg);
|
||||
Wire.endTransmission();
|
||||
@ -25,25 +25,34 @@ void BaseSensor::writeCommand(int reg) const {
|
||||
}
|
||||
|
||||
SensorData Si7021::read() {
|
||||
uint8_t error = 0;
|
||||
writeCommand(0xf3); // command to measure temperature
|
||||
Wire.requestFrom(dev_addr, 2);
|
||||
if (Wire.available() < 2) {
|
||||
PRINTLN("Si7021: 0xf3: could not read 2 bytes");
|
||||
}
|
||||
uint16_t temp_raw = Wire.read() << 8 | Wire.read();
|
||||
double temperature = ((175.72 * temp_raw) / 65536.0) - 46.85;
|
||||
|
||||
writeCommand(0xf5); // command to measure humidity
|
||||
Wire.requestFrom(dev_addr, 2);
|
||||
if (Wire.available() < 2) {
|
||||
PRINTLN("Si7021: 0xf5: could not read 2 bytes");
|
||||
}
|
||||
uint16_t hum_raw = Wire.read() << 8 | Wire.read();
|
||||
double humidity = ((125.0 * hum_raw) / 65536.0) - 6.0;
|
||||
|
||||
return {
|
||||
.temp = temperature,
|
||||
.rh = humidity
|
||||
.error = error,
|
||||
.temp = temperature,
|
||||
.rh = humidity
|
||||
};
|
||||
}
|
||||
|
||||
SensorData DHT12::read() {
|
||||
SensorData sd;
|
||||
byte raw[5];
|
||||
sd.error = 1;
|
||||
|
||||
writeCommand(0);
|
||||
Wire.requestFrom(dev_addr, 5);
|
||||
@ -69,6 +78,8 @@ SensorData DHT12::read() {
|
||||
|
||||
sd.rh = raw[0] + raw[1] * 0.1;
|
||||
|
||||
sd.error = 0;
|
||||
|
||||
end:
|
||||
return sd;
|
||||
}
|
@ -5,33 +5,34 @@
|
||||
namespace homekit::temphum {
|
||||
|
||||
struct SensorData {
|
||||
uint8_t error = 0;
|
||||
double temp = 0; // celsius
|
||||
double rh = 0; // relative humidity percentage
|
||||
};
|
||||
|
||||
|
||||
class BaseSensor {
|
||||
class Sensor {
|
||||
protected:
|
||||
int dev_addr;
|
||||
public:
|
||||
explicit BaseSensor(int dev) : dev_addr(dev) {}
|
||||
explicit Sensor(int dev) : dev_addr(dev) {}
|
||||
void setup() const;
|
||||
void writeCommand(int reg) const;
|
||||
virtual SensorData read() = 0;
|
||||
};
|
||||
|
||||
|
||||
class Si7021 : public BaseSensor {
|
||||
class Si7021 : public Sensor {
|
||||
public:
|
||||
SensorData read() override;
|
||||
Si7021() : BaseSensor(0x40) {}
|
||||
Si7021() : Sensor(0x40) {}
|
||||
};
|
||||
|
||||
|
||||
class DHT12 : public BaseSensor {
|
||||
class DHT12 : public Sensor {
|
||||
public:
|
||||
SensorData read() override;
|
||||
DHT12() : BaseSensor(0x5c) {}
|
||||
DHT12() : Sensor(0x5c) {}
|
||||
};
|
||||
|
||||
}
|
8
platformio/common/libs/temphum/library.json
Normal file
8
platformio/common/libs/temphum/library.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "homekit_temphum",
|
||||
"version": "1.0.2",
|
||||
"build": {
|
||||
"flags": "-I../../include"
|
||||
}
|
||||
}
|
||||
|
3
platformio/dumb_mqtt/.gitignore
vendored
Normal file
3
platformio/dumb_mqtt/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.pio
|
||||
CMakeListsPrivate.txt
|
||||
cmake-build-*/
|
12
platformio/dumb_mqtt/src/main.cpp
Normal file
12
platformio/dumb_mqtt/src/main.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
#include <homekit/main.h>
|
||||
|
||||
using namespace homekit;
|
||||
|
||||
void setup() {
|
||||
main::setup();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
main::loop(nullptr);
|
||||
}
|
@ -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,109 +1,26 @@
|
||||
#include <Arduino.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <DNSServer.h>
|
||||
#include <Ticker.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#include <homekit/config.h>
|
||||
#include <homekit/logging.h>
|
||||
#ifndef CONFIG_TARGET_ESP01
|
||||
#include <homekit/http_server.h>
|
||||
#endif
|
||||
#include <homekit/wifi.h>
|
||||
|
||||
#include "mqtt.h"
|
||||
#include "leds.h"
|
||||
#include "temphum.h"
|
||||
#include <homekit/main.h>
|
||||
#include <homekit/mqtt/mqtt.h>
|
||||
#include <homekit/mqtt/module/temphum.h>
|
||||
#include <homekit/temphum.h>
|
||||
|
||||
using namespace homekit;
|
||||
using main::LoopConfig;
|
||||
using mqtt::Mqtt;
|
||||
using mqtt::MqttTemphumModule;
|
||||
|
||||
#ifndef CONFIG_TARGET_ESP01
|
||||
enum class WorkingMode {
|
||||
RECOVERY, // AP mode, http server with configuration
|
||||
NORMAL, // MQTT client
|
||||
temphum::Sensor* sensor = nullptr;
|
||||
MqttTemphumModule* mqttTemphumModule = nullptr;
|
||||
|
||||
static void onMqttCreated(Mqtt& mqtt);
|
||||
|
||||
LoopConfig loopConfig = {
|
||||
.onMqttCreated = onMqttCreated
|
||||
};
|
||||
static enum WorkingMode working_mode = WorkingMode::NORMAL;
|
||||
|
||||
static const uint16_t recovery_boot_detection_ms = 2000;
|
||||
static const uint8_t recovery_boot_delay_ms = 100;
|
||||
#endif
|
||||
|
||||
enum class WiFiConnectionState {
|
||||
WAITING = 0,
|
||||
JUST_CONNECTED = 1,
|
||||
CONNECTED = 2
|
||||
};
|
||||
|
||||
static volatile enum WiFiConnectionState wifi_state = WiFiConnectionState::WAITING;
|
||||
static void* service = nullptr;
|
||||
static WiFiEventHandler wifiConnectHandler, wifiDisconnectHandler;
|
||||
static Ticker wifiTimer;
|
||||
temphum::BaseSensor* sensor = nullptr;
|
||||
|
||||
#if MQTT_BLINK
|
||||
static StopWatch blinkStopWatch;
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_TARGET_ESP01
|
||||
static DNSServer* dnsServer = nullptr;
|
||||
#endif
|
||||
|
||||
static void onWifiConnected(const WiFiEventStationModeGotIP& event);
|
||||
static void onWifiDisconnected(const WiFiEventStationModeDisconnected& event);
|
||||
|
||||
static void wifiConnect() {
|
||||
const char *ssid, *psk, *hostname;
|
||||
auto cfg = config::read();
|
||||
wifi::getConfig(cfg, &ssid, &psk, &hostname);
|
||||
|
||||
PRINTF("Wi-Fi STA creds: ssid=%s, psk=%s, hostname=%s\n", ssid, psk, hostname);
|
||||
|
||||
wifi_state = WiFiConnectionState::WAITING;
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.hostname(hostname);
|
||||
WiFi.begin(ssid, psk);
|
||||
|
||||
PRINT("connecting to wifi..");
|
||||
}
|
||||
|
||||
#ifndef CONFIG_TARGET_ESP01
|
||||
static void wifiHotspot() {
|
||||
mcu_led->on();
|
||||
|
||||
auto scanResults = wifi::scan();
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(wifi::AP_SSID);
|
||||
|
||||
dnsServer = new DNSServer();
|
||||
dnsServer->start(53, "*", WiFi.softAPIP());
|
||||
|
||||
service = new HttpServer(scanResults);
|
||||
((HttpServer*)service)->start();
|
||||
}
|
||||
|
||||
static void waitForRecoveryPress() {
|
||||
pinMode(CONFIG_FLASH_GPIO, INPUT_PULLUP);
|
||||
for (uint16_t i = 0; i < recovery_boot_detection_ms; i += recovery_boot_delay_ms) {
|
||||
delay(recovery_boot_delay_ms);
|
||||
if (digitalRead(CONFIG_FLASH_GPIO) == LOW) {
|
||||
working_mode = WorkingMode::RECOVERY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
WiFi.disconnect();
|
||||
#ifndef CONFIG_TARGET_ESP01
|
||||
waitForRecoveryPress();
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
Serial.begin(115200);
|
||||
#endif
|
||||
main::setup();
|
||||
|
||||
#if CONFIG_MODULE == HOMEKIT_SI7021
|
||||
sensor = new temphum::Si7021();
|
||||
@ -111,111 +28,15 @@ void setup() {
|
||||
sensor = new temphum::DHT12();
|
||||
#endif
|
||||
sensor->setup();
|
||||
|
||||
auto cfg = config::read();
|
||||
if (config::isDirty(cfg)) {
|
||||
PRINTLN("config is dirty, erasing...");
|
||||
config::erase(cfg);
|
||||
#ifdef CONFIG_TARGET_NODEMCU
|
||||
board_led->blink(10, 50);
|
||||
#else
|
||||
mcu_led->blink(10, 50);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef CONFIG_TARGET_ESP01
|
||||
switch (working_mode) {
|
||||
case WorkingMode::RECOVERY:
|
||||
wifiHotspot();
|
||||
break;
|
||||
|
||||
case WorkingMode::NORMAL:
|
||||
#endif
|
||||
wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnected);
|
||||
wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnected);
|
||||
wifiConnect();
|
||||
#ifndef CONFIG_TARGET_ESP01
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop() {
|
||||
#ifndef CONFIG_TARGET_ESP01
|
||||
if (working_mode == WorkingMode::NORMAL) {
|
||||
#endif
|
||||
if (wifi_state == WiFiConnectionState::WAITING) {
|
||||
PRINT(".");
|
||||
mcu_led->blink(2, 50);
|
||||
delay(1000);
|
||||
return;
|
||||
}
|
||||
main::loop(&loopConfig);
|
||||
}
|
||||
|
||||
if (wifi_state == WiFiConnectionState::JUST_CONNECTED) {
|
||||
#ifdef CONFIG_TARGET_NODEMCU
|
||||
board_led->blink(3, 300);
|
||||
#endif
|
||||
wifi_state = WiFiConnectionState::CONNECTED;
|
||||
|
||||
if (service == nullptr)
|
||||
service = new mqtt::MQTT();
|
||||
|
||||
((mqtt::MQTT*)service)->connect();
|
||||
#if MQTT_BLINK
|
||||
blinkStopWatch.save();
|
||||
#endif
|
||||
}
|
||||
|
||||
auto mqtt = (mqtt::MQTT*)service;
|
||||
if (static_cast<int>(wifi_state) >= 1 && mqtt != nullptr) {
|
||||
mqtt->loop();
|
||||
|
||||
if (mqtt->ota.readyToRestart) {
|
||||
mqtt->disconnect();
|
||||
|
||||
} else if (mqtt->diagnosticsStopWatch.elapsed(10000)) {
|
||||
mqtt->sendDiagnostics();
|
||||
|
||||
auto data = sensor->read();
|
||||
PRINT("temp:");
|
||||
PRINT(data.temp);
|
||||
PRINT(", rh: ");
|
||||
PRINTLN(data.rh);
|
||||
|
||||
mqtt->sendTempHumData(data.temp, data.rh);
|
||||
}
|
||||
|
||||
#if MQTT_BLINK
|
||||
// periodically blink board led
|
||||
if (blinkStopWatch.elapsed(5000)) {
|
||||
#ifdef CONFIG_TARGET_NODEMCU
|
||||
board_led->blink(1, 10);
|
||||
#endif
|
||||
blinkStopWatch.save();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#ifndef CONFIG_TARGET_ESP01
|
||||
} else {
|
||||
if (dnsServer != nullptr)
|
||||
dnsServer->processNextRequest();
|
||||
|
||||
auto httpServer = (HttpServer*)service;
|
||||
if (httpServer != nullptr)
|
||||
httpServer->loop();
|
||||
static void onMqttCreated(Mqtt& mqtt) {
|
||||
if (mqttTemphumModule == nullptr) {
|
||||
mqttTemphumModule = new MqttTemphumModule(sensor);
|
||||
mqtt.addModule(mqttTemphumModule);
|
||||
}
|
||||
#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);
|
||||
}
|
@ -1,316 +0,0 @@
|
||||
#include <ESP8266httpUpdate.h>
|
||||
#include <homekit/logging.h>
|
||||
#include <homekit/config.h>
|
||||
#include <homekit/util.h>
|
||||
#include <homekit/wifi.h>
|
||||
|
||||
#include "mqtt.h"
|
||||
#include "leds.h"
|
||||
|
||||
namespace homekit::mqtt {
|
||||
|
||||
static const char TOPIC_DIAGNOSTICS[] = "stat";
|
||||
static const char TOPIC_INITIAL_DIAGNOSTICS[] = "stat1";
|
||||
static const char TOPIC_OTA_RESPONSE[] = "otares";
|
||||
static const char TOPIC_TEMPHUM_DATA[] = "data";
|
||||
static const char TOPIC_ADMIN_OTA[] = "admin/ota";
|
||||
static const uint16_t MQTT_KEEPALIVE = 30;
|
||||
|
||||
enum class IncomingMessage {
|
||||
UNKNOWN,
|
||||
OTA
|
||||
};
|
||||
|
||||
using namespace espMqttClientTypes;
|
||||
|
||||
#define MD5_SIZE 16
|
||||
|
||||
MQTT::MQTT() {
|
||||
auto cfg = config::read();
|
||||
homeId = String(cfg.flags.node_configured ? cfg.node_id : wifi::NODE_ID);
|
||||
|
||||
randomSeed(micros());
|
||||
|
||||
client.onConnect([&](bool sessionPresent) {
|
||||
PRINTLN("mqtt: connected");
|
||||
|
||||
sendInitialDiagnostics();
|
||||
subscribe(TOPIC_ADMIN_OTA);
|
||||
});
|
||||
|
||||
client.onDisconnect([&](DisconnectReason reason) {
|
||||
PRINTF("mqtt: disconnected, reason=%d\n", static_cast<int>(reason));
|
||||
#ifdef DEBUG
|
||||
if (reason == DisconnectReason::TLS_BAD_FINGERPRINT)
|
||||
PRINTLN("reason: bad fingerprint");
|
||||
#endif
|
||||
|
||||
if (ota.started()) {
|
||||
PRINTLN("mqtt: update was in progress, canceling..");
|
||||
ota.clean();
|
||||
Update.end();
|
||||
Update.clearError();
|
||||
}
|
||||
|
||||
if (ota.readyToRestart) {
|
||||
restartTimer.once(1, restart);
|
||||
} else {
|
||||
reconnectTimer.once(2, [&]() {
|
||||
reconnect();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
client.onSubscribe([&](uint16_t packetId, const SubscribeReturncode* returncodes, size_t len) {
|
||||
PRINTF("mqtt: subscribe ack, packet_id=%d\n", packetId);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
PRINTF(" return code: %u\n", static_cast<unsigned int>(*(returncodes+i)));
|
||||
}
|
||||
});
|
||||
|
||||
client.onUnsubscribe([&](uint16_t packetId) {
|
||||
PRINTF("mqtt: unsubscribe ack, packet_id=%d\n", packetId);
|
||||
});
|
||||
|
||||
client.onMessage([&](const MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) {
|
||||
PRINTF("mqtt: message received, topic=%s, qos=%d, dup=%d, retain=%d, len=%ul, index=%ul, total=%ul\n",
|
||||
topic, properties.qos, (int)properties.dup, (int)properties.retain, len, index, total);
|
||||
|
||||
IncomingMessage msgType = IncomingMessage::UNKNOWN;
|
||||
|
||||
const char *ptr = topic + homeId.length() + 10;
|
||||
String relevantTopic(ptr);
|
||||
|
||||
if (relevantTopic == TOPIC_ADMIN_OTA)
|
||||
msgType = IncomingMessage::OTA;
|
||||
|
||||
if (len != total && msgType != IncomingMessage::OTA) {
|
||||
PRINTLN("mqtt: received partial message, not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msgType) {
|
||||
case IncomingMessage::OTA:
|
||||
if (ota.finished)
|
||||
break;
|
||||
handleAdminOtaPayload(properties.packetId, payload, len, index, total);
|
||||
break;
|
||||
|
||||
case IncomingMessage::UNKNOWN:
|
||||
PRINTF("error: invalid topic %s\n", topic);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
client.onPublish([&](uint16_t packetId) {
|
||||
PRINTF("mqtt: publish ack, packet_id=%d\n", packetId);
|
||||
|
||||
if (ota.finished && packetId == ota.publishResultPacketId) {
|
||||
ota.readyToRestart = true;
|
||||
}
|
||||
});
|
||||
|
||||
client.setServer(MQTT_SERVER, MQTT_PORT);
|
||||
client.setClientId(MQTT_CLIENT_ID);
|
||||
client.setCredentials(MQTT_USERNAME, MQTT_PASSWORD);
|
||||
client.setCleanSession(true);
|
||||
client.setFingerprint(MQTT_CA_FINGERPRINT);
|
||||
client.setKeepAlive(MQTT_KEEPALIVE);
|
||||
}
|
||||
|
||||
void MQTT::connect() {
|
||||
reconnect();
|
||||
}
|
||||
|
||||
void MQTT::reconnect() {
|
||||
if (client.connected()) {
|
||||
PRINTLN("warning: already connected");
|
||||
return;
|
||||
}
|
||||
client.connect();
|
||||
}
|
||||
|
||||
void MQTT::disconnect() {
|
||||
// TODO test how this works???
|
||||
reconnectTimer.detach();
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
uint16_t MQTT::publish(const String &topic, uint8_t *payload, size_t length) {
|
||||
String fullTopic = "hk/" + homeId + "/temphum/" + topic;
|
||||
return client.publish(fullTopic.c_str(), 1, false, payload, length);
|
||||
}
|
||||
|
||||
void MQTT::loop() {
|
||||
client.loop();
|
||||
}
|
||||
|
||||
uint16_t MQTT::subscribe(const String &topic, uint8_t qos) {
|
||||
String fullTopic = "hk/" + homeId + "/temphum/" + topic;
|
||||
PRINTF("mqtt: subscribing to %s...\n", fullTopic.c_str());
|
||||
|
||||
uint16_t packetId = client.subscribe(fullTopic.c_str(), qos);
|
||||
if (!packetId)
|
||||
PRINTF("error: failed to subscribe to %s\n", fullTopic.c_str());
|
||||
return packetId;
|
||||
}
|
||||
|
||||
void MQTT::sendInitialDiagnostics() {
|
||||
auto cfg = config::read();
|
||||
InitialDiagnosticsPayload stat{
|
||||
.ip = wifi::getIPAsInteger(),
|
||||
.fw_version = CONFIG_FW_VERSION,
|
||||
.rssi = wifi::getRSSI(),
|
||||
.free_heap = ESP.getFreeHeap(),
|
||||
.flags = DiagnosticsFlags{
|
||||
.state = 1,
|
||||
.config_changed_value_present = 1,
|
||||
.config_changed = static_cast<uint8_t>(cfg.flags.node_configured ||
|
||||
cfg.flags.wifi_configured ? 1 : 0)
|
||||
}
|
||||
};
|
||||
publish(TOPIC_INITIAL_DIAGNOSTICS, reinterpret_cast<uint8_t*>(&stat), sizeof(stat));
|
||||
diagnosticsStopWatch.save();
|
||||
}
|
||||
|
||||
void MQTT::sendDiagnostics() {
|
||||
DiagnosticsPayload stat{
|
||||
.rssi = wifi::getRSSI(),
|
||||
.free_heap = ESP.getFreeHeap(),
|
||||
.flags = DiagnosticsFlags{
|
||||
.state = 1,
|
||||
.config_changed_value_present = 0,
|
||||
.config_changed = 0
|
||||
}
|
||||
};
|
||||
publish(TOPIC_DIAGNOSTICS, reinterpret_cast<uint8_t*>(&stat), sizeof(stat));
|
||||
diagnosticsStopWatch.save();
|
||||
}
|
||||
|
||||
void MQTT::sendTempHumData(double temp, double rh) {
|
||||
TempHumDataPayload data {
|
||||
.temp = temp,
|
||||
.rh = rh
|
||||
};
|
||||
publish(TOPIC_TEMPHUM_DATA, reinterpret_cast<uint8_t*>(&data), sizeof(data));
|
||||
}
|
||||
|
||||
uint16_t MQTT::sendOtaResponse(OTAResult status, uint8_t error_code) {
|
||||
OTAResponse resp{
|
||||
.status = status,
|
||||
.error_code = error_code
|
||||
};
|
||||
return publish(TOPIC_OTA_RESPONSE, reinterpret_cast<uint8_t*>(&resp), sizeof(resp));
|
||||
}
|
||||
|
||||
void MQTT::handleAdminOtaPayload(uint16_t packetId, const uint8_t *payload, size_t length, size_t index, size_t total) {
|
||||
char md5[33];
|
||||
char* md5Ptr = md5;
|
||||
|
||||
if (index != 0 && ota.dataPacketId != packetId) {
|
||||
PRINTLN("mqtt/ota: non-matching packet id");
|
||||
return;
|
||||
}
|
||||
|
||||
Update.runAsync(true);
|
||||
|
||||
if (index == 0) {
|
||||
if (length < CONFIG_NODE_SECRET_SIZE + MD5_SIZE) {
|
||||
PRINTLN("mqtt/ota: failed to check secret, first packet size is too small");
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp((const char*)payload, CONFIG_NODE_SECRET, CONFIG_NODE_SECRET_SIZE) != 0) {
|
||||
PRINTLN("mqtt/ota: invalid secret");
|
||||
return;
|
||||
}
|
||||
|
||||
PRINTF("mqtt/ota: starting update, total=%ul\n", total-CONFIG_NODE_SECRET_SIZE);
|
||||
for (int i = 0; i < MD5_SIZE; i++) {
|
||||
md5Ptr += sprintf(md5Ptr, "%02x", *((unsigned char*)(payload+CONFIG_NODE_SECRET_SIZE+i)));
|
||||
}
|
||||
md5[32] = '\0';
|
||||
PRINTF("mqtt/ota: md5 is %s\n", md5);
|
||||
PRINTF("mqtt/ota: first packet is %ul bytes length\n", length);
|
||||
|
||||
md5[32] = '\0';
|
||||
|
||||
if (Update.isRunning()) {
|
||||
Update.end();
|
||||
Update.clearError();
|
||||
}
|
||||
|
||||
if (!Update.setMD5(md5)) {
|
||||
PRINTLN("mqtt/ota: setMD5 failed");
|
||||
return;
|
||||
}
|
||||
|
||||
ota.dataPacketId = packetId;
|
||||
|
||||
if (!Update.begin(total - CONFIG_NODE_SECRET_SIZE - MD5_SIZE)) {
|
||||
ota.clean();
|
||||
#ifdef DEBUG
|
||||
Update.printError(Serial);
|
||||
#endif
|
||||
sendOtaResponse(OTAResult::UPDATE_ERROR, Update.getError());
|
||||
}
|
||||
|
||||
ota.written = Update.write(const_cast<uint8_t*>(payload)+CONFIG_NODE_SECRET_SIZE + MD5_SIZE, length-CONFIG_NODE_SECRET_SIZE - MD5_SIZE);
|
||||
ota.written += CONFIG_NODE_SECRET_SIZE + MD5_SIZE;
|
||||
|
||||
mcu_led->blink(1, 1);
|
||||
PRINTF("mqtt/ota: updating %u/%u\n", ota.written, Update.size());
|
||||
|
||||
} else {
|
||||
if (!Update.isRunning()) {
|
||||
PRINTLN("mqtt/ota: update is not running");
|
||||
return;
|
||||
}
|
||||
|
||||
if (index == ota.written) {
|
||||
size_t written;
|
||||
if ((written = Update.write(const_cast<uint8_t*>(payload), length)) != length) {
|
||||
PRINTF("mqtt/ota: error: tried to write %ul bytes, write() returned %ul\n",
|
||||
length, written);
|
||||
ota.clean();
|
||||
Update.end();
|
||||
Update.clearError();
|
||||
sendOtaResponse(OTAResult::WRITE_ERROR);
|
||||
return;
|
||||
}
|
||||
ota.written += length;
|
||||
|
||||
mcu_led->blink(1, 1);
|
||||
PRINTF("mqtt/ota: updating %u/%u\n",
|
||||
ota.written - CONFIG_NODE_SECRET_SIZE - MD5_SIZE,
|
||||
Update.size());
|
||||
} else {
|
||||
PRINTF("mqtt/ota: position is invalid, expected %ul, got %ul\n", ota.written, index);
|
||||
ota.clean();
|
||||
Update.end();
|
||||
Update.clearError();
|
||||
}
|
||||
}
|
||||
|
||||
if (Update.isFinished()) {
|
||||
ota.dataPacketId = 0;
|
||||
|
||||
if (Update.end()) {
|
||||
ota.finished = true;
|
||||
ota.publishResultPacketId = sendOtaResponse(OTAResult::OK);
|
||||
PRINTF("mqtt/ota: ok, otares packet_id=%d\n", ota.publishResultPacketId);
|
||||
} else {
|
||||
ota.clean();
|
||||
|
||||
PRINTF("mqtt/ota: error: %u\n", Update.getError());
|
||||
#ifdef DEBUG
|
||||
Update.printError(Serial);
|
||||
#endif
|
||||
Update.clearError();
|
||||
|
||||
sendOtaResponse(OTAResult::UPDATE_ERROR, Update.getError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
#ifndef HOMEKIT_TEMPHUM_MQTT_H
|
||||
#define HOMEKIT_TEMPHUM_MQTT_H
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <espMqttClient.h>
|
||||
#include <Ticker.h>
|
||||
|
||||
#include <homekit/stopwatch.h>
|
||||
#include <homekit/mqtt.h>
|
||||
|
||||
namespace homekit { namespace mqtt {
|
||||
|
||||
enum class OTAResult: uint8_t {
|
||||
OK = 0,
|
||||
UPDATE_ERROR = 1,
|
||||
WRITE_ERROR = 2,
|
||||
};
|
||||
|
||||
struct OTAStatus {
|
||||
uint16_t dataPacketId;
|
||||
uint16_t publishResultPacketId;
|
||||
bool finished;
|
||||
bool readyToRestart;
|
||||
size_t written;
|
||||
|
||||
OTAStatus()
|
||||
: dataPacketId(0)
|
||||
, publishResultPacketId(0)
|
||||
, finished(false)
|
||||
, readyToRestart(false)
|
||||
, written(0)
|
||||
{}
|
||||
|
||||
inline void clean() {
|
||||
dataPacketId = 0;
|
||||
publishResultPacketId = 0;
|
||||
finished = false;
|
||||
readyToRestart = false;
|
||||
written = 0;
|
||||
}
|
||||
|
||||
inline bool started() const {
|
||||
return dataPacketId != 0;
|
||||
}
|
||||
};
|
||||
|
||||
class MQTT {
|
||||
private:
|
||||
String homeId;
|
||||
WiFiClientSecure httpsSecureClient;
|
||||
espMqttClientSecure client;
|
||||
Ticker reconnectTimer;
|
||||
Ticker restartTimer;
|
||||
|
||||
void handleAdminOtaPayload(uint16_t packetId, const uint8_t* payload, size_t length, size_t index, size_t total);
|
||||
|
||||
uint16_t publish(const String& topic, uint8_t* payload, size_t length);
|
||||
uint16_t subscribe(const String& topic, uint8_t qos = 0);
|
||||
|
||||
void sendInitialDiagnostics();
|
||||
uint16_t sendOtaResponse(OTAResult status, uint8_t error_code = 0);
|
||||
|
||||
public:
|
||||
StopWatch diagnosticsStopWatch;
|
||||
OTAStatus ota;
|
||||
|
||||
MQTT();
|
||||
void connect();
|
||||
void disconnect();
|
||||
void reconnect();
|
||||
void loop();
|
||||
void sendDiagnostics();
|
||||
void sendTempHumData(double temp, double rh);
|
||||
};
|
||||
|
||||
struct DiagnosticsFlags {
|
||||
uint8_t state: 1;
|
||||
uint8_t config_changed_value_present: 1;
|
||||
uint8_t config_changed: 1;
|
||||
uint8_t reserved: 5;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct InitialDiagnosticsPayload {
|
||||
uint32_t ip;
|
||||
uint8_t fw_version;
|
||||
int8_t rssi;
|
||||
uint32_t free_heap;
|
||||
DiagnosticsFlags flags;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct DiagnosticsPayload {
|
||||
int8_t rssi;
|
||||
uint32_t free_heap;
|
||||
DiagnosticsFlags flags;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PowerPayload {
|
||||
char secret[12];
|
||||
uint8_t state;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct TempHumDataPayload {
|
||||
double temp;
|
||||
double rh;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct OTAResponse {
|
||||
OTAResult status;
|
||||
uint8_t error_code;
|
||||
} __attribute__((packed));
|
||||
|
||||
} }
|
||||
|
||||
#endif //HOMEKIT_TEMPHUM_MQTT_H
|
@ -4,7 +4,7 @@ two_digits_precision = lambda x: round(x, 2)
|
||||
|
||||
|
||||
class TempHumDataPayload(MqttPayload):
|
||||
FORMAT = '=dd'
|
||||
FORMAT = '=ddb'
|
||||
UNPACKER = {
|
||||
'temp': two_digits_precision,
|
||||
'rh': two_digits_precision
|
||||
@ -12,3 +12,4 @@ class TempHumDataPayload(MqttPayload):
|
||||
|
||||
temp: float
|
||||
rh: float
|
||||
error: int
|
||||
|
Loading…
x
Reference in New Issue
Block a user