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
|
.idea
|
||||||
|
.vscode
|
||||||
/venv
|
/venv
|
||||||
/node_modules
|
/node_modules
|
||||||
*.pyc
|
*.pyc
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include "led.h"
|
#include "led.h"
|
||||||
|
|
||||||
namespace homekit {
|
namespace homekit::led {
|
||||||
|
|
||||||
void Led::on_off(uint16_t delay_ms, bool last_delay) const {
|
void Led::on_off(uint16_t delay_ms, bool last_delay) const {
|
||||||
on();
|
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
|
#ifndef HOMEKIT_LIB_LED_H
|
||||||
#define COMMON_HOMEKIT_LED_H
|
#define HOMEKIT_LIB_LED_H
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
namespace homekit {
|
namespace homekit::led {
|
||||||
|
|
||||||
class Led {
|
class Led {
|
||||||
private:
|
private:
|
||||||
@ -23,6 +23,11 @@ public:
|
|||||||
void blink(uint8_t count, uint16_t delay_ms) const;
|
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",
|
"name": "homekit_led",
|
||||||
"version": "1.0.6",
|
"version": "1.0.8",
|
||||||
"build": {
|
"build": {
|
||||||
"flags": "-I../../include"
|
"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",
|
"name": "homekit_mqtt",
|
||||||
"version": "1.0.2",
|
"version": "1.0.8",
|
||||||
"build": {
|
"build": {
|
||||||
"flags": "-I../../include"
|
"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 {
|
namespace homekit::temphum {
|
||||||
|
|
||||||
void BaseSensor::setup() const {
|
void Sensor::setup() const {
|
||||||
#ifndef CONFIG_TARGET_ESP01
|
#ifndef CONFIG_TARGET_ESP01
|
||||||
pinMode(CONFIG_SDA_GPIO, OUTPUT);
|
pinMode(CONFIG_SDA_GPIO, OUTPUT);
|
||||||
pinMode(CONFIG_SCL_GPIO, OUTPUT);
|
pinMode(CONFIG_SCL_GPIO, OUTPUT);
|
||||||
@ -17,7 +17,7 @@ void BaseSensor::setup() const {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseSensor::writeCommand(int reg) const {
|
void Sensor::writeCommand(int reg) const {
|
||||||
Wire.beginTransmission(dev_addr);
|
Wire.beginTransmission(dev_addr);
|
||||||
Wire.write(reg);
|
Wire.write(reg);
|
||||||
Wire.endTransmission();
|
Wire.endTransmission();
|
||||||
@ -25,25 +25,34 @@ void BaseSensor::writeCommand(int reg) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SensorData Si7021::read() {
|
SensorData Si7021::read() {
|
||||||
|
uint8_t error = 0;
|
||||||
writeCommand(0xf3); // command to measure temperature
|
writeCommand(0xf3); // command to measure temperature
|
||||||
Wire.requestFrom(dev_addr, 2);
|
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();
|
uint16_t temp_raw = Wire.read() << 8 | Wire.read();
|
||||||
double temperature = ((175.72 * temp_raw) / 65536.0) - 46.85;
|
double temperature = ((175.72 * temp_raw) / 65536.0) - 46.85;
|
||||||
|
|
||||||
writeCommand(0xf5); // command to measure humidity
|
writeCommand(0xf5); // command to measure humidity
|
||||||
Wire.requestFrom(dev_addr, 2);
|
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();
|
uint16_t hum_raw = Wire.read() << 8 | Wire.read();
|
||||||
double humidity = ((125.0 * hum_raw) / 65536.0) - 6.0;
|
double humidity = ((125.0 * hum_raw) / 65536.0) - 6.0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
.temp = temperature,
|
.error = error,
|
||||||
.rh = humidity
|
.temp = temperature,
|
||||||
|
.rh = humidity
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
SensorData DHT12::read() {
|
SensorData DHT12::read() {
|
||||||
SensorData sd;
|
SensorData sd;
|
||||||
byte raw[5];
|
byte raw[5];
|
||||||
|
sd.error = 1;
|
||||||
|
|
||||||
writeCommand(0);
|
writeCommand(0);
|
||||||
Wire.requestFrom(dev_addr, 5);
|
Wire.requestFrom(dev_addr, 5);
|
||||||
@ -69,6 +78,8 @@ SensorData DHT12::read() {
|
|||||||
|
|
||||||
sd.rh = raw[0] + raw[1] * 0.1;
|
sd.rh = raw[0] + raw[1] * 0.1;
|
||||||
|
|
||||||
|
sd.error = 0;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
return sd;
|
return sd;
|
||||||
}
|
}
|
@ -5,33 +5,34 @@
|
|||||||
namespace homekit::temphum {
|
namespace homekit::temphum {
|
||||||
|
|
||||||
struct SensorData {
|
struct SensorData {
|
||||||
|
uint8_t error = 0;
|
||||||
double temp = 0; // celsius
|
double temp = 0; // celsius
|
||||||
double rh = 0; // relative humidity percentage
|
double rh = 0; // relative humidity percentage
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class BaseSensor {
|
class Sensor {
|
||||||
protected:
|
protected:
|
||||||
int dev_addr;
|
int dev_addr;
|
||||||
public:
|
public:
|
||||||
explicit BaseSensor(int dev) : dev_addr(dev) {}
|
explicit Sensor(int dev) : dev_addr(dev) {}
|
||||||
void setup() const;
|
void setup() const;
|
||||||
void writeCommand(int reg) const;
|
void writeCommand(int reg) const;
|
||||||
virtual SensorData read() = 0;
|
virtual SensorData read() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class Si7021 : public BaseSensor {
|
class Si7021 : public Sensor {
|
||||||
public:
|
public:
|
||||||
SensorData read() override;
|
SensorData read() override;
|
||||||
Si7021() : BaseSensor(0x40) {}
|
Si7021() : Sensor(0x40) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class DHT12 : public BaseSensor {
|
class DHT12 : public Sensor {
|
||||||
public:
|
public:
|
||||||
SensorData read() override;
|
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 <Arduino.h>
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#include <DNSServer.h>
|
|
||||||
#include <Ticker.h>
|
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
|
#include <homekit/main.h>
|
||||||
#include <homekit/config.h>
|
#include <homekit/mqtt/mqtt.h>
|
||||||
#include <homekit/logging.h>
|
#include <homekit/mqtt/module/temphum.h>
|
||||||
#ifndef CONFIG_TARGET_ESP01
|
#include <homekit/temphum.h>
|
||||||
#include <homekit/http_server.h>
|
|
||||||
#endif
|
|
||||||
#include <homekit/wifi.h>
|
|
||||||
|
|
||||||
#include "mqtt.h"
|
|
||||||
#include "leds.h"
|
|
||||||
#include "temphum.h"
|
|
||||||
|
|
||||||
using namespace homekit;
|
using namespace homekit;
|
||||||
|
using main::LoopConfig;
|
||||||
|
using mqtt::Mqtt;
|
||||||
|
using mqtt::MqttTemphumModule;
|
||||||
|
|
||||||
#ifndef CONFIG_TARGET_ESP01
|
temphum::Sensor* sensor = nullptr;
|
||||||
enum class WorkingMode {
|
MqttTemphumModule* mqttTemphumModule = nullptr;
|
||||||
RECOVERY, // AP mode, http server with configuration
|
|
||||||
NORMAL, // MQTT client
|
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() {
|
void setup() {
|
||||||
WiFi.disconnect();
|
main::setup();
|
||||||
#ifndef CONFIG_TARGET_ESP01
|
|
||||||
waitForRecoveryPress();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
Serial.begin(115200);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if CONFIG_MODULE == HOMEKIT_SI7021
|
#if CONFIG_MODULE == HOMEKIT_SI7021
|
||||||
sensor = new temphum::Si7021();
|
sensor = new temphum::Si7021();
|
||||||
@ -111,111 +28,15 @@ void setup() {
|
|||||||
sensor = new temphum::DHT12();
|
sensor = new temphum::DHT12();
|
||||||
#endif
|
#endif
|
||||||
sensor->setup();
|
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() {
|
void loop() {
|
||||||
#ifndef CONFIG_TARGET_ESP01
|
main::loop(&loopConfig);
|
||||||
if (working_mode == WorkingMode::NORMAL) {
|
}
|
||||||
#endif
|
|
||||||
if (wifi_state == WiFiConnectionState::WAITING) {
|
|
||||||
PRINT(".");
|
|
||||||
mcu_led->blink(2, 50);
|
|
||||||
delay(1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wifi_state == WiFiConnectionState::JUST_CONNECTED) {
|
static void onMqttCreated(Mqtt& mqtt) {
|
||||||
#ifdef CONFIG_TARGET_NODEMCU
|
if (mqttTemphumModule == nullptr) {
|
||||||
board_led->blink(3, 300);
|
mqttTemphumModule = new MqttTemphumModule(sensor);
|
||||||
#endif
|
mqtt.addModule(mqttTemphumModule);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
#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):
|
class TempHumDataPayload(MqttPayload):
|
||||||
FORMAT = '=dd'
|
FORMAT = '=ddb'
|
||||||
UNPACKER = {
|
UNPACKER = {
|
||||||
'temp': two_digits_precision,
|
'temp': two_digits_precision,
|
||||||
'rh': two_digits_precision
|
'rh': two_digits_precision
|
||||||
@ -12,3 +12,4 @@ class TempHumDataPayload(MqttPayload):
|
|||||||
|
|
||||||
temp: float
|
temp: float
|
||||||
rh: float
|
rh: float
|
||||||
|
error: int
|
||||||
|
Loading…
x
Reference in New Issue
Block a user