Discovery HA and HomeD

This commit is contained in:
Mit4el
2024-09-20 12:19:15 +03:00
parent d6a24b1837
commit b32abb5a28
12 changed files with 782 additions and 39 deletions

View File

@@ -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<JsonArray>();
for (JsonVariant value : arr)
{
String dev = selectToMarkerLast(value["topic"].as<String>(), "/");
dev.replace(":", "");
String HAjson = "";
HAjson = "{\"availability\":[{\"topic\": \"" + mqttRootDevice + "/state\",\"value_template\": \"{{ value_json.status }}\"}],\"availability_mode\": \"any\",";
HAjson = HAjson + " \"device\": {\"identifiers\": [\"" + value["page"].as<String>() + "\"],";
HAjson = HAjson + " \"name\": \" " + value["page"].as<String>() + "\"},";
HAjson = HAjson + " \"name\": \"" + value["descr"].as<String>() + "\",";
HAjson = HAjson + " \"state_topic\": \"" + value["topic"].as<String>() + "/status\",";
HAjson = HAjson + " \"icon\": \"hass:none\",";
// сенсоры
if (value["name"].as<String>() == "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<String>() == "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<String>() == "inputDgt")
{
HAjson = HAjson + " \"value_template\": \"{{ float( value_json.status, default = 0) | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + dev + "\",";
HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as<String>() + "/control\",";
HAjson = HAjson + " \"mode\": \"box\",";
HAjson = HAjson + " \"min\": " + -1000000 + ",";
HAjson = HAjson + " \"max\": " + 1000000 + "";
}
// ввод текста inputTxt
else if (value["name"].as<String>() == "inputTxt")
{
HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + dev + "\",";
HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as<String>() + "/control\"";
}
// переключатель
else if (value["name"].as<String>() == "toggle")
{
HAjson = HAjson + " \"value_template\": \"{{ value_json.status | default }}\",";
HAjson = HAjson + " \"unique_id\": \"" + dev + "\",";
HAjson = HAjson + " \"command_topic\": \"" + value["topic"].as<String>() + "/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<String>() == "anydata")
{
if (!publishRetain(HATopic + "/sensor/" + chipId + "/" + dev + "/config", HAjson))
{
SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant"));
}
}
// ввод числа
if (value["name"].as<String>() == "inputDgt")
{
if (!publishRetain(HATopic + "/number/" + chipId + "/" + dev + "/config", HAjson))
{
SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant"));
}
}
// ввод текста inputTxt
if (value["name"].as<String>() == "inputTxt")
{
if (!publishRetain(HATopic + "/text/" + chipId + "/" + dev + "/config", HAjson))
{
SerialPrint("E", F("MQTT"), F("Failed publish data to homeassitant"));
}
}
// переключатель
if (value["name"].as<String>() == "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<IoTItem *>::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;
}
}

View File

@@ -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*": []
}
}

View File

@@ -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<JsonObject>())
{
String key = kvp.key().c_str();
SerialPrint("i", F("=>MQTT"), "Msg from HOMEd: " + key);
String value = kvp.value().as<const char *>();
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<JsonArray>();
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>(), "/");
String id = chipId + "-" + device;
String expose = value["name"];
if (value["name"].as<String>() == "toggle")
{
HOMEdJSON = HOMEdJSON + "\"switch_" + device + "\",";
}
else
{
HOMEdJSON = HOMEdJSON + "\"" + device + "\",";
}
if (value["name"].as<String>() == "anydataTmp")
{
// HOMEdJSON = HOMEdJSON + "\"temperature_" + device + "\",";
options = options + "\"" + device + "\":{\"type\": \"sensor\", \"class\": \"temperature\", \"state\": \"measurement\", \"unit\": \"°C\", \"round\": 1},";
}
if (value["name"].as<String>() == "anydataHum")
{
// HOMEdJSON = HOMEdJSON + "\"humidity_" + device + "\",";
options = options + "\"" + device + "\":{\"type\": \"sensor\", \"class\": \"humidity\", \"state\": \"measurement\", \"unit\": \"%\", \"round\": 1},";
}
if (value["name"].as<String>() == "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<IoTItem *>::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<IoTItem *>::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;
}
}

View File

@@ -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*": []
}
}