From b32abb5a28c9cfcf9c677ec62f1a4e0116cdcd97 Mon Sep 17 00:00:00 2001 From: Mit4el Date: Fri, 20 Sep 2024 12:19:15 +0300 Subject: [PATCH] Discovery HA and HomeD --- include/Global.h | 16 + include/classes/IoTDiscovery.h | 36 +++ include/classes/IoTItem.h | 2 + src/ESPConfiguration.cpp | 14 +- src/Global.cpp | 5 + src/MqttClient.cpp | 111 ++++--- src/classes/IoTDiscovery.cpp | 32 ++ src/classes/IoTItem.cpp | 8 + .../virtual/DiscoveryHA/DiscoveryHA.cpp | 242 +++++++++++++++ src/modules/virtual/DiscoveryHA/modinfo.json | 39 +++ .../virtual/DiscoveryHomeD/DiscoveryHomeD.cpp | 277 ++++++++++++++++++ .../virtual/DiscoveryHomeD/modinfo.json | 39 +++ 12 files changed, 782 insertions(+), 39 deletions(-) create mode 100644 include/classes/IoTDiscovery.h create mode 100644 src/classes/IoTDiscovery.cpp create mode 100644 src/modules/virtual/DiscoveryHA/DiscoveryHA.cpp create mode 100644 src/modules/virtual/DiscoveryHA/modinfo.json create mode 100644 src/modules/virtual/DiscoveryHomeD/DiscoveryHomeD.cpp create mode 100644 src/modules/virtual/DiscoveryHomeD/modinfo.json diff --git a/include/Global.h b/include/Global.h index f748a4ba..8053cf7e 100644 --- a/include/Global.h +++ b/include/Global.h @@ -9,6 +9,15 @@ #include #include +#ifdef libretiny +#include +#include +#ifdef STANDARD_WEB_SERVER +#include +#endif +#include +#endif + #ifdef ESP32 #include "WiFi.h" #include @@ -22,6 +31,7 @@ #ifdef ASYNC_WEB_SERVER #include +#include "AsyncWebServer.h" #endif #ifdef STANDARD_WEB_SERVER @@ -60,6 +70,9 @@ extern IoTItem* rtcItem; extern IoTItem* tlgrmItem; extern IoTBench* benchLoadItem; extern IoTBench* benchTaskItem; +extern IoTDiscovery* HADiscovery; +extern IoTDiscovery* HOMEdDiscovery; + extern TickerScheduler ts; extern WiFiClient espClient; @@ -77,6 +90,9 @@ extern ESP8266HTTPUpdateServer httpUpdater; #ifdef ESP32 extern WebServer HTTP; #endif +#ifdef libretiny +extern WebServer HTTP; +#endif #endif #ifdef STANDARD_WEB_SOCKETS diff --git a/include/classes/IoTDiscovery.h b/include/classes/IoTDiscovery.h new file mode 100644 index 00000000..f8c9610a --- /dev/null +++ b/include/classes/IoTDiscovery.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include "Global.h" +#include "classes/IoTItem.h" + +class IoTDiscovery : public IoTItem +{ +public: + IoTDiscovery(const String ¶meters); + ~IoTDiscovery(); + +// inline bool isDiscoveryHomed() { return HOMEd; } + +// inline bool isDiscoveryHA() { return HA; } + + String HOMEdTopic = ""; + String HATopic = ""; + //String ChipId = ""; + + virtual void mqttSubscribeDiscovery(); + + virtual void publishStatusHOMEd(const String &topic, const String &data); + + + +protected: + boolean publishRetain(const String &topic, const String &data); + virtual void getlayoutHA(); + virtual void deleteFromHOMEd(); + virtual void getlayoutHOMEd(); + + //bool HOMEd = false; + //bool HA = false; + //String HOMEdTopic; + +}; \ No newline at end of file diff --git a/include/classes/IoTItem.h b/include/classes/IoTItem.h index cfdb33e2..23b63644 100644 --- a/include/classes/IoTItem.h +++ b/include/classes/IoTItem.h @@ -59,6 +59,8 @@ class IoTItem { //virtual IoTBench* getBenchmark(); virtual IoTBench*getBenchmarkTask(); virtual IoTBench*getBenchmarkLoad(); + virtual IoTBench*getHADiscovery(); + virtual IoTBench*getHOMEdDiscovery(); virtual unsigned long getRtcUnixTime(); // делаем доступным модулям отправку сообщений в телеграм diff --git a/src/ESPConfiguration.cpp b/src/ESPConfiguration.cpp index fa15b2a1..5f11b6ba 100644 --- a/src/ESPConfiguration.cpp +++ b/src/ESPConfiguration.cpp @@ -8,6 +8,10 @@ void* getAPI(String subtype, String params); void configure(String path) { File file = seekFile(path); + if (!file) { + SerialPrint(F("E"), F("FS"), F("configure file open error")); + return; + } file.find("["); while (file.available()) { String jsonArrayElement = file.readStringUntil('}') + "}"; @@ -36,6 +40,9 @@ void configure(String path) { // пробуем спросить драйвер Benchmark if (driver = myIoTItem->getBenchmarkTask()) benchTaskItem = ((IoTBench*)driver); if (driver = myIoTItem->getBenchmarkLoad()) benchLoadItem = ((IoTBench*)driver); + // пробуем спросить драйвер для интеграций + if (driver = myIoTItem->getHOMEdDiscovery()) HOMEdDiscovery = ((IoTDiscovery*)driver); + if (driver = myIoTItem->getHADiscovery()) HADiscovery = ((IoTDiscovery*)driver); // пробуем спросить драйвер Telegram_v2 if (driver = myIoTItem->getTlgrmDriver()) tlgrmItem = (IoTItem*)driver; IoTItems.push_back(myIoTItem); @@ -59,8 +66,13 @@ void clearConfigure() { if (*it) delete *it; } IoTItems.clear(); - +#ifdef libretiny + valuesFlashJson.remove(0, valuesFlashJson.length()); +#else valuesFlashJson.clear(); +#endif benchTaskItem = nullptr; benchLoadItem = nullptr; + HOMEdDiscovery = nullptr; + HADiscovery = nullptr; } \ No newline at end of file diff --git a/src/Global.cpp b/src/Global.cpp index 7f6fe4d1..6fe4dfcd 100644 --- a/src/Global.cpp +++ b/src/Global.cpp @@ -20,6 +20,9 @@ ESP8266WebServer HTTP(80); #ifdef ESP32 WebServer HTTP(80); #endif +#ifdef libretiny +WebServer HTTP(80); +#endif #endif #ifdef STANDARD_WEB_SOCKETS @@ -35,6 +38,8 @@ IoTItem* rtcItem = nullptr; IoTItem* tlgrmItem = nullptr; IoTBench* benchTaskItem = nullptr; IoTBench* benchLoadItem = nullptr; +IoTDiscovery* HOMEdDiscovery = nullptr; +IoTDiscovery* HADiscovery = nullptr; String settingsFlashJson = "{}"; // переменная в которой хранятся все настройки, находится в оперативной памяти и синхронизированна с flash памятью String valuesFlashJson = "{}"; // переменная в которой хранятся все значения элементов, которые необходимо сохранить на flash. Находится в оперативной памяти и синхронизированна с flash памятью String errorsHeapJson = "{}"; // переменная в которой хранятся все ошибки, находится в оперативной памяти только diff --git a/src/MqttClient.cpp b/src/MqttClient.cpp index 727473ad..a2b31747 100644 --- a/src/MqttClient.cpp +++ b/src/MqttClient.cpp @@ -1,4 +1,5 @@ #include "MqttClient.h" +#include "classes/IoTDiscovery.h" void mqttInit() { mqtt.setCallback(mqttCallback); @@ -59,10 +60,24 @@ boolean mqttConnect() { if (!mqtt.connected()) { bool connected = false; if (mqttUser != "" && mqttPass != "") { - connected = mqtt.connect(chipId.c_str(), mqttUser.c_str(), mqttPass.c_str()); + if (HOMEdDiscovery) + { + connected = mqtt.connect(chipId.c_str(), mqttUser.c_str(), mqttPass.c_str(), (HOMEdDiscovery->HOMEdTopic + "/device/custom/" + chipId).c_str(), 1, true, "{\"status\":\"offline\"}"); + } + else + { + connected = mqtt.connect(chipId.c_str(), mqttUser.c_str(), mqttPass.c_str(), (mqttRootDevice + "/state").c_str(), 1, true, "{\"status\":\"offline\"}"); + } SerialPrint("i", F("MQTT"), F("Go to connection with login and password")); } else if (mqttUser == "" && mqttPass == "") { - connected = mqtt.connect(chipId.c_str()); + if (HOMEdDiscovery) + { + connected = mqtt.connect(chipId.c_str(), (HOMEdDiscovery->HOMEdTopic + "/device/custom/" + chipId).c_str(), 1, true, "{\"status\":\"offline\"}"); + } + else + { + connected = mqtt.connect(chipId.c_str(), (mqttRootDevice + "/state").c_str(), 1, true, "{\"status\":\"offline\"}"); + } SerialPrint("i", F("MQTT"), F("Go to connection without login and password")); } else { SerialPrint("E", F("MQTT"), F("✖ Login or password missed")); @@ -129,17 +144,33 @@ void mqttSubscribe() { } } } + if(HOMEdDiscovery) + HOMEdDiscovery->mqttSubscribeDiscovery(); + if(HADiscovery) + HADiscovery->mqttSubscribeDiscovery(); + // оттправляем все статусы + if(HOMEdDiscovery || HADiscovery) + { + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->iAmLocal) + { + publishStatusMqtt((*it)->getID(), (*it)->getValue()); + (*it)->onMqttWsAppConnectEvent(); + } + } + } } void mqttSubscribeExternal(String topic, bool usePrefix) { - // SerialPrint("i", F("MQTT"), mqttRootDevice); - String _sb_topic = topic; - if (usePrefix) - { - _sb_topic = mqttPrefix + "/" + topic; - } - mqtt.subscribe(_sb_topic.c_str()); + // SerialPrint("i", F("MQTT"), mqttRootDevice); + String _sb_topic = topic; + if (usePrefix) + { + _sb_topic = mqttPrefix + "/" + topic; + } + mqtt.subscribe(_sb_topic.c_str()); SerialPrint("i", F("MQTT"), ("subscribed external " + _sb_topic).c_str()); } @@ -253,6 +284,10 @@ boolean publishChartMqtt(const String& topic, const String& data) { } boolean publishStatusMqtt(const String& topic, const String& data) { + if (HOMEdDiscovery) + { + HOMEdDiscovery->publishStatusHOMEd(topic, data); + } String path = mqttRootDevice + "/" + topic + "/status"; String json = "{}"; jsonWriteStr(json, "status", data); @@ -322,47 +357,47 @@ void handleMqttStatus(bool send, int state) { const String getStateStr(int e) { switch (e) { case -4: // Нет ответа от сервера - return F("e1"); - break; + return F("e1"); + break; case -3: // Соединение было разорвано - return F("e2"); - break; + return F("e2"); + break; case -2: // Ошибка соединения. Обычно возникает когда неверно указано название сервера MQTT - return F("e3"); - break; + return F("e3"); + break; case -1: // Клиент был отключен - return F("e4"); - break; + return F("e4"); + break; case 0: // подключено - return F("e5"); - break; + return F("e5"); + break; case 1: // Ошибка версии - return F("e6"); - break; + return F("e6"); + break; case 2: // Отклонен идентификатор - return F("e7"); - break; + return F("e7"); + break; case 3: // Не могу установить соединение - return F("e8"); - break; + return F("e8"); + break; case 4: // Неправильное имя пользователя/пароль - return F("e9"); - break; + return F("e9"); + break; case 5: // Не авторизован для подключения - return F("e10"); - break; + return F("e10"); + break; case 6: // Название сервера пустое - return F("e11"); - break; + return F("e11"); + break; case 7: // Имя пользователя или пароль пустые - return F("e12"); - break; + return F("e12"); + break; case 8: // Подключение в процессе - return F("e13"); - break; - default: - return F("unk"); - break; + return F("e13"); + break; + default: + return F("unk"); + break; } } diff --git a/src/classes/IoTDiscovery.cpp b/src/classes/IoTDiscovery.cpp new file mode 100644 index 00000000..734bb560 --- /dev/null +++ b/src/classes/IoTDiscovery.cpp @@ -0,0 +1,32 @@ +#include "Global.h" +#include "classes/IoTDiscovery.h" +#include "IoTDiscovery.h" + +IoTDiscovery::IoTDiscovery(const String ¶meters) : IoTItem(parameters) +{ + /* int _tx, _rx, _speed, _line; + jsonRead(parameters, "rx", _rx); + jsonRead(parameters, "tx", _tx); + jsonRead(parameters, "speed", _speed); + jsonRead(parameters, "line", _line); + */ + //ChipId = getChipId(); +} + +void IoTDiscovery::publishStatusHOMEd(const String &topic, const String &data) {} +void IoTDiscovery::getlayoutHA() {} +void IoTDiscovery::getlayoutHOMEd() {} +void IoTDiscovery::deleteFromHOMEd() {} +void IoTDiscovery::mqttSubscribeDiscovery(){} + +boolean IoTDiscovery::publishRetain(const String &topic, const String &data) +{ + if (mqtt.beginPublish(topic.c_str(), data.length(), true)) + { + mqtt.print(data); + return mqtt.endPublish(); + } + return false; +} + +IoTDiscovery::~IoTDiscovery() {} diff --git a/src/classes/IoTItem.cpp b/src/classes/IoTItem.cpp index fd078a6f..b6cb26d5 100644 --- a/src/classes/IoTItem.cpp +++ b/src/classes/IoTItem.cpp @@ -258,6 +258,14 @@ IoTBench *IoTItem::getBenchmarkLoad() { return nullptr; } +IoTBench *IoTItem::getHOMEdDiscovery() +{ + return nullptr; +} +IoTBench *IoTItem::getHADiscovery() +{ + return nullptr; +} unsigned long IoTItem::getRtcUnixTime() { return 0; diff --git a/src/modules/virtual/DiscoveryHA/DiscoveryHA.cpp b/src/modules/virtual/DiscoveryHA/DiscoveryHA.cpp new file mode 100644 index 00000000..ff3b571c --- /dev/null +++ b/src/modules/virtual/DiscoveryHA/DiscoveryHA.cpp @@ -0,0 +1,242 @@ +#include "Global.h" +#include "classes/IoTDiscovery.h" + +class DiscoveryHA : public IoTDiscovery +{ +private: + String _topic = ""; + bool sendOk = false; + // bool topicOk = false; + bool HA = false; +public: + DiscoveryHA(String parameters) : IoTDiscovery(parameters) + { + _topic = jsonReadStr(parameters, "topic"); + if (_topic && _topic != "" && _topic != "null") + { + HA = true; + HATopic = _topic; + } + if (mqttIsConnect() && HA) + { +#if defined ESP32 +// пре реконнекте вызывается отправка всех виджетов из файла layout.json в HA +// на ESP8266 мало оперативки и это можно делать только до момента конфигурации +// mqttReconnect(); +#endif + // sendOk = true; + // mqttSubscribeExternal(_topic); + } + } +/* + void onMqttRecive(String &topic, String &msg) + { + if (!HA) + return; + + if (msg.indexOf("HELLO") == -1) + { + String dev = selectToMarkerLast(topic, "/"); + dev.toUpperCase(); + dev.replace(":", ""); + if (_topic != topic) + { + // SerialPrint("i", "ExternalMQTT", _id + " not equal: " + topic + " msg: " + msg); + return; + } + // обработка топика, на который подписались + } + } */ + + void doByInterval() + { + /* // периодически проверяем связь с MQTT брокером и если она появилась, то подписываемся на нужный топик + if (mqttIsConnect() && !sendOk && &&topicOk) + { + sendOk = true; + getlayoutHA(); + publishRetain(mqttRootDevice + "/state", "{\"status\":\"online\"}"); + //mqttSubscribeExternal(_topic); + } + + // если нет коннектас брокером, то сбрасываем флаг подписки, что бы при реконекте заново подписаться + if (!mqttIsConnect()) + sendOk = false; */ + } + /* String getMqttExterSub() + { + return _topic; + } */ + + void mqttSubscribeDiscovery() + { + if (HA) + { + getlayoutHA(); + publishRetain(mqttRootDevice + "/state", "{\"status\":\"online\"}"); + } + } + + void getlayoutHA() + { + if (HA) + { + auto file = seekFile("layout.json"); + if (!file) + { + SerialPrint("E", F("MQTT"), F("no file layout.json")); + return; + } + size_t size = file.size(); + DynamicJsonDocument doc(size * 2); + DeserializationError error = deserializeJson(doc, file); + if (error) + { + SerialPrint("E", F("MQTT"), error.f_str()); + jsonWriteInt(errorsHeapJson, F("jse3"), 1); // Ошибка чтения json файла с виджетами при отправки в mqtt + } + int i = 0; + // String path = jsonReadStr(settingsFlashJson, F("HomeAssistant")); + JsonArray arr = doc.as(); + for (JsonVariant value : arr) + { + String dev = selectToMarkerLast(value["topic"].as(), "/"); + dev.replace(":", ""); + String HAjson = ""; + HAjson = "{\"availability\":[{\"topic\": \"" + mqttRootDevice + "/state\",\"value_template\": \"{{ value_json.status }}\"}],\"availability_mode\": \"any\","; + HAjson = HAjson + " \"device\": {\"identifiers\": [\"" + value["page"].as() + "\"],"; + HAjson = HAjson + " \"name\": \" " + value["page"].as() + "\"},"; + HAjson = HAjson + " \"name\": \"" + value["descr"].as() + "\","; + HAjson = HAjson + " \"state_topic\": \"" + value["topic"].as() + "/status\","; + HAjson = HAjson + " \"icon\": \"hass:none\","; + + // сенсоры + if (value["name"].as() == "anydataTmp") + { + HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + dev + "\","; + HAjson = HAjson + " \"state_class\": \"measurement\","; + HAjson = HAjson + " \"unit_of_measurement\": \"°C\""; + } + else if (value["name"].as() == "anydataHum") + { + HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + dev + "\","; + HAjson = HAjson + " \"state_class\": \"measurement\","; + HAjson = HAjson + " \"unit_of_measurement\": \"%\""; + } + // ввод числа + else if (value["name"].as() == "inputDgt") + { + HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + dev + "\","; + HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as() + "/control\","; + HAjson = HAjson + " \"mode\": \"box\","; + HAjson = HAjson + " \"min\": " + -1000000 + ","; + HAjson = HAjson + " \"max\": " + 1000000 + ""; + } + // ввод текста inputTxt + else if (value["name"].as() == "inputTxt") + { + HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + dev + "\","; + HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as() + "/control\""; + } + // переключатель + else if (value["name"].as() == "toggle") + { + HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + dev + "\","; + HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as() + "/control\","; + HAjson = HAjson + " \"device_class\": \"switch\","; + HAjson = HAjson + " \"payload_off\": " + 0 + ","; + HAjson = HAjson + " \"payload_on\": " + 1 + ","; + HAjson = HAjson + " \"state_off\": " + 0 + ","; + HAjson = HAjson + " \"state_on\": " + 1 + ""; + } + else + { + HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\","; + HAjson = HAjson + " \"unique_id\": \"" + dev + "\""; + } + + HAjson = HAjson + " }"; + // "has_entity_name" : false, + + // SerialPrint("E", F("MQTT"), HAjson); + // текст + if (value["widget"].as() == "anydata") + { + + if (!publishRetain(HATopic + "/sensor/" + chipId + "/" + dev + "/config", HAjson)) + { + SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant")); + } + } + + // ввод числа + if (value["name"].as() == "inputDgt") + { + + if (!publishRetain(HATopic + "/number/" + chipId + "/" + dev + "/config", HAjson)) + { + SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant")); + } + } + // ввод текста inputTxt + if (value["name"].as() == "inputTxt") + { + + if (!publishRetain(HATopic + "/text/" + chipId + "/" + dev + "/config", HAjson)) + { + SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant")); + } + } + // переключатель + if (value["name"].as() == "toggle") + { + + if (!publishRetain(HATopic + "/switch/" + chipId + "/" + dev + "/config", HAjson)) + { + SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant")); + } + } + + i++; + } + file.close(); + + publishRetain(mqttRootDevice + "/state", "{\"status\":\"online\"}"); + + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->iAmLocal) + { + publishStatusMqtt((*it)->getID(), (*it)->getValue()); + (*it)->onMqttWsAppConnectEvent(); + } + } + } + } + + IoTDiscovery *getBenchmarkTask() + { + if (HA) + return this; + else + return nullptr; + } + ~DiscoveryHA(){}; +}; + +void *getAPI_DiscoveryHA(String subtype, String param) +{ + if (subtype == F("DiscoveryHA")) + { + return new DiscoveryHA(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/virtual/DiscoveryHA/modinfo.json b/src/modules/virtual/DiscoveryHA/modinfo.json new file mode 100644 index 00000000..cf71635f --- /dev/null +++ b/src/modules/virtual/DiscoveryHA/modinfo.json @@ -0,0 +1,39 @@ +{ + "menuSection": "virtual", + "configItem": [ + { + "global": 0, + "name": "Discovery of HA", + "type": "Reading", + "subtype": "DiscoveryHA", + "id": "ha", + "widget": "", + "page": "", + "descr": "", + "topic": "homeassistant" + } + ], + "about": { + "authorName": "Bubnov Mikhail", + "authorContact": "https://t.me/Mit4bmw", + "authorGit": "https://github.com/Mit4el", + "specialThanks": "@AVAKS", + "moduleName": "DiscoveryHA", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "DiscoveryHA", + "moduleDesc": "Модуль проброса данных в MQTT для HomeAssistant", + "propInfo": { + "topic":"Топик HomeAssistant" + } + + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp82*": [] + } +} \ No newline at end of file diff --git a/src/modules/virtual/DiscoveryHomeD/DiscoveryHomeD.cpp b/src/modules/virtual/DiscoveryHomeD/DiscoveryHomeD.cpp new file mode 100644 index 00000000..07088ba6 --- /dev/null +++ b/src/modules/virtual/DiscoveryHomeD/DiscoveryHomeD.cpp @@ -0,0 +1,277 @@ +#include "Global.h" +#include "classes/IoTDiscovery.h" +// #include "MqttDiscovery.h" +class DiscoveryHomeD : public IoTDiscovery +{ +private: + String _topic = ""; + bool sendOk = false; + // bool topicOk = false; + bool HOMEd = false; + +public: + DiscoveryHomeD(String parameters) : IoTDiscovery(parameters) + { + _topic = jsonReadStr(parameters, "topic"); + if (_topic && _topic != "" && _topic != "null") + { + HOMEd = true; + HOMEdTopic = _topic; + } + if (mqttIsConnect() && HOMEd) + { + mqttReconnect(); + // sendOk = true; + // mqttSubscribeExternal(_topic); + } + } + + void onMqttRecive(String &topic, String &payloadStr) + { + if (!HOMEd) + return; + + if (msg.indexOf("HELLO") == -1) + { +/* String dev = selectToMarkerLast(topic, "/"); + dev.toUpperCase(); + dev.replace(":", ""); + if (_topic != topic) + { + // SerialPrint("i", "ExternalMQTT", _id + " not equal: " + topic + " msg: " + msg); + return; + } */ + // обработка топика, на который подписались + if (topic.indexOf(F("/td/custom")) != -1) + { + + // обрабатываем команды из HOMEd + StaticJsonDocument<200> doc; + deserializeJson(doc, payloadStr); + for (JsonPair kvp : doc.as()) + { + + String key = kvp.key().c_str(); + SerialPrint("i", F("=>MQTT"), "Msg from HOMEd: " + key); + String value = kvp.value().as(); + if (key.indexOf(F("status_")) != -1) + { + key.replace("status_", ""); + if (value == "on") + { + generateOrder(key, "1"); + } + else if (value == "off") + { + generateOrder(key, "0"); + } + else if (value == "toggle") + { + String val = (String)(1 - getItemValue(key).toInt()); + generateOrder(key, val); + } + } + + if (!value) + { + float val = kvp.value(); + generateOrder(key, (String)(val)); + } + } + + SerialPrint("i", F("=>MQTT"), "Msg from HOMEd: " + payloadStr); + } + } + } + + void doByInterval() + { + /* // периодически проверяем связь с MQTT брокером и если она появилась, то подписываемся на нужный топик + if (mqttIsConnect() && !sendOk && topicOk) + { + sendOk = true; + publishRetain(_topic + "/device/custom/" + chipId, "{\"status\":\"online\"}"); + String HOMEdsubscribeTopic = _topic + "/td/custom/" + chipId; + // mqtt.subscribe(HOMEdsubscribeTopic.c_str()); + mqttSubscribeExternal(HOMEdsubscribeTopic); + } + + // если нет коннектас брокером, то сбрасываем флаг подписки, что бы при реконекте заново подписаться + if (!mqttIsConnect()) + sendOk = false; */ + } + + void publishStatusHOMEd(const String &topic, const String &data) + { + String path_h = HOMEdTopic + "/fd/custom/" + chipId; + String json_h = "{}"; + if (topic != "onStart") + { + if (data.toInt() == 1) + { + jsonWriteStr(json_h, "status_" + topic, "on"); + } + else if (data.toInt() == 0) + { + jsonWriteStr(json_h, "status_" + topic, "off"); + } + if (data.toFloat()) + { + jsonWriteFloat(json_h, topic, data.toFloat()); + } + else + { + jsonWriteStr(json_h, topic, data); + } + mqtt.publish(path_h.c_str(), json_h.c_str(), false); + } + } + + void mqttSubscribeDiscovery() + { + if (HOMEd) + { + deleteFromHOMEd(); + getlayoutHOMEd(); + publishRetain(HOMEdTopic + "/device/custom/" + chipId, "{\"status\":\"online\"}"); + String HOMEdsubscribeTopic = HOMEdTopic + "/td/custom/" + chipId; + mqtt.subscribe(HOMEdsubscribeTopic.c_str()); + } + } + + void getlayoutHOMEd() + { + if (HOMEd) + { + String devName = jsonReadStr(settingsFlashJson, F("name")); + + auto file = seekFile("layout.json"); + if (!file) + { + SerialPrint("E", F("MQTT"), F("no file layout.json")); + return; + } + size_t size = file.size(); + DynamicJsonDocument doc(size * 2); + DeserializationError error = deserializeJson(doc, file); + if (error) + { + SerialPrint("E", F("MQTT"), error.f_str()); + jsonWriteInt(errorsHeapJson, F("jse3"), 1); // Ошибка чтения json файла с виджетами при отправки в mqtt + } + int i = 0; + // String path = jsonReadStr(settingsFlashJson, F("HOMEd")); + JsonArray arr = doc.as(); + String HOMEdJSON = ""; + HOMEdJSON = "{\"action\":\"updateDevice\","; + HOMEdJSON = HOMEdJSON + "\"device\":\"" + chipId + "\","; + HOMEdJSON = HOMEdJSON + "\"data\":{"; + HOMEdJSON = HOMEdJSON + "\"active\": true,"; + HOMEdJSON = HOMEdJSON + "\"cloud\": false,"; + HOMEdJSON = HOMEdJSON + "\"discovery\": false,"; + HOMEdJSON = HOMEdJSON + "\"id\":\"" + chipId + "\","; + HOMEdJSON = HOMEdJSON + "\"name\":\"" + devName + "\","; + HOMEdJSON = HOMEdJSON + "\"real\":true,"; + HOMEdJSON = HOMEdJSON + "\"exposes\": ["; + String options = ""; + for (JsonVariant value : arr) + { + String name = value["descr"]; + String device = selectToMarkerLast(value["topic"].as(), "/"); + String id = chipId + "-" + device; + String expose = value["name"]; + if (value["name"].as() == "toggle") + { + HOMEdJSON = HOMEdJSON + "\"switch_" + device + "\","; + } + else + { + HOMEdJSON = HOMEdJSON + "\"" + device + "\","; + } + + if (value["name"].as() == "anydataTmp") + { + // HOMEdJSON = HOMEdJSON + "\"temperature_" + device + "\","; + options = options + "\"" + device + "\":{\"type\": \"sensor\", \"class\": \"temperature\", \"state\": \"measurement\", \"unit\": \"°C\", \"round\": 1},"; + } + if (value["name"].as() == "anydataHum") + { + // HOMEdJSON = HOMEdJSON + "\"humidity_" + device + "\","; + options = options + "\"" + device + "\":{\"type\": \"sensor\", \"class\": \"humidity\", \"state\": \"measurement\", \"unit\": \"%\", \"round\": 1},"; + } + if (value["name"].as() == "inputDgt") + { + options = options + "\"" + device + "\":{\"type\": \"number\", \"min\": -10000, \"max\": 100000, \"step\": 0.1},"; + } + + i++; + } + options = options.substring(0, options.length() - 1); + HOMEdJSON = HOMEdJSON.substring(0, HOMEdJSON.length() - 1); + HOMEdJSON = HOMEdJSON + "],"; + HOMEdJSON = HOMEdJSON + " \"options\": {" + options + "}"; + HOMEdJSON = HOMEdJSON + "}}"; + String topic = (HOMEdTopic + "/command/custom").c_str(); + if (!publish(topic, HOMEdJSON)) + { + SerialPrint("E", F("MQTT"), F("Failed publish data to HOMEd")); + } + + file.close(); + + publishRetain(HOMEdTopic + "/device/custom/" + chipId, "{\"status\":\"online\"}"); + + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if ((*it)->iAmLocal) + { + publishStatusMqtt((*it)->getID(), (*it)->getValue()); + (*it)->onMqttWsAppConnectEvent(); + } + } + } + } + void deleteFromHOMEd() + { + if (HOMEd) + { + for (std::list::iterator it = IoTItems.begin(); it != IoTItems.end(); ++it) + { + if (*it) + { + String id_widget = (*it)->getID().c_str(); + String HOMEdjson = ""; + HOMEdjson = "{\"action\":\"removeDevice\","; + HOMEdjson = HOMEdjson + "\"device\":\""; + HOMEdjson = HOMEdjson + chipId; + HOMEdjson = HOMEdjson + "\"}"; + String topic = (HOMEdTopic + "/command/custom").c_str(); + if (!publish(topic, HOMEdjson)) + { + SerialPrint("E", F("MQTT"), F("Failed remove from HOMEd")); + } + } + } + } + } + IoTBench *getBenchmarkTask() + { + if (HOMEd) + return this; + else + return nullptr; + } + ~DiscoveryHomeD(){}; +}; + +void *getAPI_DiscoveryHomeD(String subtype, String param) +{ + if (subtype == F("DiscoveryHomeD")) + { + return new DiscoveryHomeD(param); + } + else + { + return nullptr; + } +} diff --git a/src/modules/virtual/DiscoveryHomeD/modinfo.json b/src/modules/virtual/DiscoveryHomeD/modinfo.json new file mode 100644 index 00000000..049d3507 --- /dev/null +++ b/src/modules/virtual/DiscoveryHomeD/modinfo.json @@ -0,0 +1,39 @@ +{ + "menuSection": "virtual", + "configItem": [ + { + "global": 0, + "name": "Discovery of HomeD", + "type": "Reading", + "subtype": "DiscoveryHomeD", + "id": "homed", + "widget": "", + "page": "", + "descr": "", + "topic": "homed" + } + ], + "about": { + "authorName": "Bubnov Mikhail", + "authorContact": "https://t.me/Mit4bmw", + "authorGit": "https://github.com/Mit4el", + "specialThanks": "@AVAKS", + "moduleName": "DiscoveryHomeD", + "moduleVersion": "1.0", + "usedRam": { + "esp32_4mb": 15, + "esp8266_4mb": 15 + }, + "title": "DiscoveryHomeD", + "moduleDesc": "Модуль проброса данных в MQTT для HOMEd-Custom", + "propInfo": { + "topic":"Топик службы HOMEd-Custom, например /myRoom/homed" + } + + }, + "defActive": false, + "usedLibs": { + "esp32*": [], + "esp82*": [] + } +} \ No newline at end of file