summaryrefslogtreecommitdiff
path: root/platformio/common
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2023-05-29 05:44:59 +0300
committerEvgeny Zinoviev <me@ch1p.io>2023-05-29 16:31:08 +0300
commit8b2088103a74e616ca82fe043de55cb7ed58e329 (patch)
treef8dad8b7bbe9cb6eb135e63cb3ece2338e035aba /platformio/common
parent6a64c97c799bd4919b46f8dc39d80799bb751ab3 (diff)
platformio: split code into libraries
Diffstat (limited to 'platformio/common')
-rw-r--r--platformio/common/libs/led/homekit/led.cpp9
-rw-r--r--platformio/common/libs/led/homekit/led.h13
-rw-r--r--platformio/common/libs/led/library.json2
-rw-r--r--platformio/common/libs/main/homekit/main.cpp194
-rw-r--r--platformio/common/libs/main/homekit/main.h48
-rw-r--r--platformio/common/libs/main/library.json12
-rw-r--r--platformio/common/libs/mqtt/homekit/mqtt.cpp18
-rw-r--r--platformio/common/libs/mqtt/homekit/mqtt.h18
-rw-r--r--platformio/common/libs/mqtt/homekit/mqtt/module.cpp26
-rw-r--r--platformio/common/libs/mqtt/homekit/mqtt/module.h47
-rw-r--r--platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp164
-rw-r--r--platformio/common/libs/mqtt/homekit/mqtt/mqtt.h48
-rw-r--r--platformio/common/libs/mqtt/homekit/mqtt/payload.h15
-rw-r--r--platformio/common/libs/mqtt/library.json3
-rw-r--r--platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.cpp47
-rw-r--r--platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.h46
-rw-r--r--platformio/common/libs/mqtt_module_diagnostics/library.json7
-rw-r--r--platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.cpp162
-rw-r--r--platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.h72
-rw-r--r--platformio/common/libs/mqtt_module_ota/library.json10
-rw-r--r--platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.cpp23
-rw-r--r--platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.h28
-rw-r--r--platformio/common/libs/mqtt_module_temphum/library.json10
-rw-r--r--platformio/common/libs/temphum/homekit/temphum.cpp87
-rw-r--r--platformio/common/libs/temphum/homekit/temphum.h38
-rw-r--r--platformio/common/libs/temphum/library.json8
26 files changed, 1111 insertions, 44 deletions
diff --git a/platformio/common/libs/led/homekit/led.cpp b/platformio/common/libs/led/homekit/led.cpp
index acc2140..ffefb04 100644
--- a/platformio/common/libs/led/homekit/led.cpp
+++ b/platformio/common/libs/led/homekit/led.cpp
@@ -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);
+
+
}
diff --git a/platformio/common/libs/led/homekit/led.h b/platformio/common/libs/led/homekit/led.h
index 513bfea..775d2eb 100644
--- a/platformio/common/libs/led/homekit/led.h
+++ b/platformio/common/libs/led/homekit/led.h
@@ -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
diff --git a/platformio/common/libs/led/library.json b/platformio/common/libs/led/library.json
index 69a3ac3..6785d42 100644
--- a/platformio/common/libs/led/library.json
+++ b/platformio/common/libs/led/library.json
@@ -1,6 +1,6 @@
{
"name": "homekit_led",
- "version": "1.0.6",
+ "version": "1.0.8",
"build": {
"flags": "-I../../include"
}
diff --git a/platformio/common/libs/main/homekit/main.cpp b/platformio/common/libs/main/homekit/main.cpp
new file mode 100644
index 0000000..fd08925
--- /dev/null
+++ b/platformio/common/libs/main/homekit/main.cpp
@@ -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);
+}
+
+} \ No newline at end of file
diff --git a/platformio/common/libs/main/homekit/main.h b/platformio/common/libs/main/homekit/main.h
new file mode 100644
index 0000000..a503dd0
--- /dev/null
+++ b/platformio/common/libs/main/homekit/main.h
@@ -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
diff --git a/platformio/common/libs/main/library.json b/platformio/common/libs/main/library.json
new file mode 100644
index 0000000..04eedab
--- /dev/null
+++ b/platformio/common/libs/main/library.json
@@ -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"
+ }
+}
+
diff --git a/platformio/common/libs/mqtt/homekit/mqtt.cpp b/platformio/common/libs/mqtt/homekit/mqtt.cpp
deleted file mode 100644
index c2ba293..0000000
--- a/platformio/common/libs/mqtt/homekit/mqtt.cpp
+++ /dev/null
@@ -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;
-
-}
diff --git a/platformio/common/libs/mqtt/homekit/mqtt.h b/platformio/common/libs/mqtt/homekit/mqtt.h
deleted file mode 100644
index 2c86b00..0000000
--- a/platformio/common/libs/mqtt/homekit/mqtt.h
+++ /dev/null
@@ -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
diff --git a/platformio/common/libs/mqtt/homekit/mqtt/module.cpp b/platformio/common/libs/mqtt/homekit/mqtt/module.cpp
new file mode 100644
index 0000000..e78ff12
--- /dev/null
+++ b/platformio/common/libs/mqtt/homekit/mqtt/module.cpp
@@ -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) {}
+
+}
diff --git a/platformio/common/libs/mqtt/homekit/mqtt/module.h b/platformio/common/libs/mqtt/homekit/mqtt/module.h
new file mode 100644
index 0000000..7bf2522
--- /dev/null
+++ b/platformio/common/libs/mqtt/homekit/mqtt/module.h
@@ -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 \ No newline at end of file
diff --git a/platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp
new file mode 100644
index 0000000..9d96f9f
--- /dev/null
+++ b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.cpp
@@ -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;
+}
+
+}
diff --git a/platformio/common/libs/mqtt/homekit/mqtt/mqtt.h b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.h
new file mode 100644
index 0000000..983cc5a
--- /dev/null
+++ b/platformio/common/libs/mqtt/homekit/mqtt/mqtt.h
@@ -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 \ No newline at end of file
diff --git a/platformio/common/libs/mqtt/homekit/mqtt/payload.h b/platformio/common/libs/mqtt/homekit/mqtt/payload.h
new file mode 100644
index 0000000..3e0fe0c
--- /dev/null
+++ b/platformio/common/libs/mqtt/homekit/mqtt/payload.h
@@ -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 \ No newline at end of file
diff --git a/platformio/common/libs/mqtt/library.json b/platformio/common/libs/mqtt/library.json
index 8131494..179b10a 100644
--- a/platformio/common/libs/mqtt/library.json
+++ b/platformio/common/libs/mqtt/library.json
@@ -1,8 +1,7 @@
{
"name": "homekit_mqtt",
- "version": "1.0.2",
+ "version": "1.0.8",
"build": {
"flags": "-I../../include"
}
}
-
diff --git a/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.cpp b/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.cpp
new file mode 100644
index 0000000..f022f79
--- /dev/null
+++ b/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.cpp
@@ -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));
+ }
+}
+
+} \ No newline at end of file
diff --git a/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.h b/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.h
new file mode 100644
index 0000000..055c179
--- /dev/null
+++ b/platformio/common/libs/mqtt_module_diagnostics/homekit/mqtt/module/diagnostics.h
@@ -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
diff --git a/platformio/common/libs/mqtt_module_diagnostics/library.json b/platformio/common/libs/mqtt_module_diagnostics/library.json
new file mode 100644
index 0000000..81bf0fb
--- /dev/null
+++ b/platformio/common/libs/mqtt_module_diagnostics/library.json
@@ -0,0 +1,7 @@
+{
+ "name": "homekit_mqtt_module_diagnostics",
+ "version": "1.0.0",
+ "build": {
+ "flags": "-I../../include"
+ }
+}
diff --git a/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.cpp b/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.cpp
new file mode 100644
index 0000000..2f5f814
--- /dev/null
+++ b/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.cpp
@@ -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;
+ }
+}
+
+}
diff --git a/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.h b/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.h
new file mode 100644
index 0000000..53613c3
--- /dev/null
+++ b/platformio/common/libs/mqtt_module_ota/homekit/mqtt/module/ota.h
@@ -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
diff --git a/platformio/common/libs/mqtt_module_ota/library.json b/platformio/common/libs/mqtt_module_ota/library.json
new file mode 100644
index 0000000..1eeda1b
--- /dev/null
+++ b/platformio/common/libs/mqtt_module_ota/library.json
@@ -0,0 +1,10 @@
+{
+ "name": "homekit_mqtt_module_ota",
+ "version": "1.0.1",
+ "build": {
+ "flags": "-I../../include"
+ },
+ "dependencies": {
+ "homekit_led": "file://../common/libs/led"
+ }
+}
diff --git a/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.cpp b/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.cpp
new file mode 100644
index 0000000..8b7a2ef
--- /dev/null
+++ b/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.cpp
@@ -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));
+}
+
+} \ No newline at end of file
diff --git a/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.h b/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.h
new file mode 100644
index 0000000..5c41cef
--- /dev/null
+++ b/platformio/common/libs/mqtt_module_temphum/homekit/mqtt/module/temphum.h
@@ -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
diff --git a/platformio/common/libs/mqtt_module_temphum/library.json b/platformio/common/libs/mqtt_module_temphum/library.json
new file mode 100644
index 0000000..30f5eb6
--- /dev/null
+++ b/platformio/common/libs/mqtt_module_temphum/library.json
@@ -0,0 +1,10 @@
+{
+ "name": "homekit_mqtt_module_temphum",
+ "version": "1.0.8",
+ "build": {
+ "flags": "-I../../include"
+ },
+ "dependencies": {
+ "homekit_temphum": "file://../common/libs/temphum"
+ }
+}
diff --git a/platformio/common/libs/temphum/homekit/temphum.cpp b/platformio/common/libs/temphum/homekit/temphum.cpp
new file mode 100644
index 0000000..ac4d371
--- /dev/null
+++ b/platformio/common/libs/temphum/homekit/temphum.cpp
@@ -0,0 +1,87 @@
+#ifndef CONFIG_TARGET_ESP01
+#include <Arduino.h>
+#endif
+#include <homekit/logging.h>
+#include "temphum.h"
+
+namespace homekit::temphum {
+
+void Sensor::setup() const {
+#ifndef CONFIG_TARGET_ESP01
+ pinMode(CONFIG_SDA_GPIO, OUTPUT);
+ pinMode(CONFIG_SCL_GPIO, OUTPUT);
+
+ Wire.begin(CONFIG_SDA_GPIO, CONFIG_SCL_GPIO);
+#else
+ Wire.begin();
+#endif
+}
+
+void Sensor::writeCommand(int reg) const {
+ Wire.beginTransmission(dev_addr);
+ Wire.write(reg);
+ Wire.endTransmission();
+ delay(500); // wait for the measurement to be ready
+}
+
+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 {
+ .error = error,
+ .temp = temperature,
+ .rh = humidity
+ };
+}
+
+SensorData DHT12::read() {
+ SensorData sd;
+ byte raw[5];
+ sd.error = 1;
+
+ writeCommand(0);
+ Wire.requestFrom(dev_addr, 5);
+
+ if (Wire.available() < 5) {
+ PRINTLN("DHT12: could not read 5 bytes");
+ goto end;
+ }
+
+ // Parse the received data
+ for (uint8_t i = 0; i < 5; i++)
+ raw[i] = Wire.read();
+
+ if (((raw[0] + raw[1] + raw[2] + raw[3]) & 0xff) != raw[4]) {
+ PRINTLN("DHT12: checksum error");
+ goto end;
+ }
+
+ // Calculate temperature and humidity values
+ sd.temp = raw[2] + (raw[3] & 0x7f) * 0.1;
+ if (raw[3] & 0x80)
+ sd.temp *= -1;
+
+ sd.rh = raw[0] + raw[1] * 0.1;
+
+ sd.error = 0;
+
+end:
+ return sd;
+}
+
+} \ No newline at end of file
diff --git a/platformio/common/libs/temphum/homekit/temphum.h b/platformio/common/libs/temphum/homekit/temphum.h
new file mode 100644
index 0000000..1952ce0
--- /dev/null
+++ b/platformio/common/libs/temphum/homekit/temphum.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <Wire.h>
+
+namespace homekit::temphum {
+
+struct SensorData {
+ uint8_t error = 0;
+ double temp = 0; // celsius
+ double rh = 0; // relative humidity percentage
+};
+
+
+class Sensor {
+protected:
+ int dev_addr;
+public:
+ explicit Sensor(int dev) : dev_addr(dev) {}
+ void setup() const;
+ void writeCommand(int reg) const;
+ virtual SensorData read() = 0;
+};
+
+
+class Si7021 : public Sensor {
+public:
+ SensorData read() override;
+ Si7021() : Sensor(0x40) {}
+};
+
+
+class DHT12 : public Sensor {
+public:
+ SensorData read() override;
+ DHT12() : Sensor(0x5c) {}
+};
+
+} \ No newline at end of file
diff --git a/platformio/common/libs/temphum/library.json b/platformio/common/libs/temphum/library.json
new file mode 100644
index 0000000..ae7f670
--- /dev/null
+++ b/platformio/common/libs/temphum/library.json
@@ -0,0 +1,8 @@
+{
+ "name": "homekit_temphum",
+ "version": "1.0.2",
+ "build": {
+ "flags": "-I../../include"
+ }
+}
+