From bbc725f31620a70daaba8def18e3b0de2298238c Mon Sep 17 00:00:00 2001 From: Kai Gerken Date: Sat, 2 Jul 2022 20:24:55 +0200 Subject: [PATCH 1/4] Implement MQTT discovery for Home Assistant --- tools/esp8266/README.md | 2 ++ tools/esp8266/app.cpp | 61 ++++++++++++++++++++++++++++++++++++++- tools/esp8266/app.h | 4 +++ tools/esp8266/defines.h | 14 +++++---- tools/esp8266/hmDefines.h | 23 +++++++++++++++ tools/esp8266/mqtt.h | 12 +++++--- 6 files changed, 105 insertions(+), 11 deletions(-) diff --git a/tools/esp8266/README.md b/tools/esp8266/README.md index 57066fc0..4d02eec1 100644 --- a/tools/esp8266/README.md +++ b/tools/esp8266/README.md @@ -16,6 +16,7 @@ This code can be compiled using Arduino. The settings were: - Time Arduino Time library (TimeLib.h) - RF24 Optimized high speed nRF24L01+ driver class documentation - PubSubClient A client library for MQTT messaging. By Nick O'Leary + - ArduinoJson Arduino Json library ### Optional Configuration before compilation @@ -65,3 +66,4 @@ For now the following inverters should work out of the box: - `Time` 1.6.1 - `RF24` 1.4.2 - `PubSubClient` 2.8 +- `ArduinoJson` 6.19.4 \ No newline at end of file diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index d45baa62..428f3dd2 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -154,7 +154,7 @@ void app::setup(uint32_t timeout) { mMqtt.setup(addr, mqttTopic, mqttUser, mqttPwd, mqttPort); mMqttTicker = 0; - + mSerialTicker = 0; if(mqttAddr[0] > 0) { @@ -284,6 +284,7 @@ void app::loop(void) { } } snprintf(val, 10, "%d", millis()/1000); + sendMqttDiscoveryConfig(); mMqtt.sendMsg("uptime", val); } @@ -904,3 +905,61 @@ void app::updateCrc(void) { DPRINTLN(DBG_DEBUG, F("new CRC: ") + String(crc, HEX)); mEep->write(ADDR_SETTINGS_CRC, crc); } + +void app::sendMqttDiscoveryConfig(void) { + DPRINTLN(DBG_VERBOSE, F("app::sendMqttDiscoveryConfig")); + + char stateTopic[64], discoveryTopic[64], buffer[512], name[32], uniq_id[32]; + for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if(NULL != iv) { + if(iv->isAvailable(mTimestamp) && mMqttConfigSendState[id] != true) { + DynamicJsonDocument deviceDoc(128); + deviceDoc["name"] = iv->name; + deviceDoc["ids"] = String(iv->serial.u64, HEX); + deviceDoc["cu"] = F("http://") + String(WiFi.localIP().toString()); + JsonObject deviceObj = deviceDoc.as(); + DynamicJsonDocument doc(384); + + for(uint8_t i = 0; i < iv->listLen; i++) { + if (iv->assign[i].ch == CH0) { + snprintf(name, 32, "%s %s", iv->name, iv->getFieldName(i)); + } else { + snprintf(name, 32, "%s CH%d %s", iv->name, iv->assign[i].ch, iv->getFieldName(i)); + } + snprintf(stateTopic, 64, "%s/%s/ch%d/%s", mMqtt.getTopic(), iv->name, iv->assign[i].ch, iv->getFieldName(i)); + snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->name, iv->assign[i].ch, iv->getFieldName(i)); + snprintf(uniq_id, 32, "ch%d_%s", iv->assign[i].ch, iv->getFieldName(i)); + const char* devCls = getFieldDeviceClass(iv->assign[i].fieldId); + + doc["name"] = name; + doc["stat_t"] = stateTopic; + doc["unit_of_meas"] = iv->getUnit(i); + doc["uniq_id"] = String(iv->serial.u64, HEX) + "_" + uniq_id; + doc["dev"] = deviceObj; + doc["exp_aft"] = mMqttInterval; + if (devCls != NULL) { + doc["dev_cla"] = devCls; + } + + serializeJson(doc, buffer); + mMqtt.sendMsg2(discoveryTopic, buffer); + doc.clear(); + + yield(); + } + + mMqttConfigSendState[id] = true; + } + } + } +} + +const char* app::getFieldDeviceClass(uint8_t fieldId) { + uint8_t pos = 0; + for(; pos < DEVICE_CLS_ASSIGN_LIST_LEN; pos++) { + if(deviceFieldAssignment[pos].fieldId == fieldId) + break; + } + return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : deviceClasses[deviceFieldAssignment[pos].deviceClsId]; +} diff --git a/tools/esp8266/app.h b/tools/esp8266/app.h index 1970ed7d..ef14b9b2 100644 --- a/tools/esp8266/app.h +++ b/tools/esp8266/app.h @@ -8,6 +8,7 @@ #include #include +#include #include "defines.h" #include "main.h" @@ -73,6 +74,8 @@ class app : public Main { void saveValues(bool webSend); void updateCrc(void); + void sendMqttDiscoveryConfig(void); + const char* getFieldDeviceClass(uint8_t fieldId); uint64_t Serial2u64(const char *val) { char tmp[3] = {0}; @@ -116,6 +119,7 @@ class app : public Main { uint16_t mMqttTicker; uint16_t mMqttInterval; bool mMqttActive; + bool mMqttConfigSendState[MAX_NUM_INVERTERS]; // serial uint16_t mSerialTicker; diff --git a/tools/esp8266/defines.h b/tools/esp8266/defines.h index 8861bab9..b5471678 100644 --- a/tools/esp8266/defines.h +++ b/tools/esp8266/defines.h @@ -50,12 +50,14 @@ typedef struct { #define RF24_AMP_PWR_LEN 1 -#define MQTT_ADDR_LEN 4 // IP -#define MQTT_USER_LEN 16 -#define MQTT_PWD_LEN 32 -#define MQTT_TOPIC_LEN 32 -#define MQTT_INTERVAL_LEN 2 // uint16_t -#define MQTT_PORT_LEN 2 // uint16_t +#define MQTT_ADDR_LEN 4 // IP +#define MQTT_USER_LEN 16 +#define MQTT_PWD_LEN 32 +#define MQTT_TOPIC_LEN 32 +#define MQTT_INTERVAL_LEN 2 // uint16_t +#define MQTT_PORT_LEN 2 // uint16_t +#define MQTT_DISCOVERY_PREFIX "homeassistant" +#define MQTT_MAX_PACKET_SIZE 384 #define SER_ENABLE_LEN 1 // uint8_t #define SER_DEBUG_LEN 1 // uint8_t diff --git a/tools/esp8266/hmDefines.h b/tools/esp8266/hmDefines.h index 38a2fc4e..3594c2f7 100644 --- a/tools/esp8266/hmDefines.h +++ b/tools/esp8266/hmDefines.h @@ -27,6 +27,29 @@ enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT, const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", "U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Effiency", "Irradiation"}; +// mqtt discovery device classes +enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP}; +const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"}; +typedef struct { + uint8_t fieldId; // field id + uint8_t deviceClsId; // device class +} byteAssign_fieldDeviceClass; +const byteAssign_fieldDeviceClass deviceFieldAssignment[] = { + {FLD_UDC, DEVICE_CLS_VOLTAGE}, + {FLD_IDC, DEVICE_CLS_CURRENT}, + {FLD_PDC, DEVICE_CLS_PWR}, + {FLD_YD, DEVICE_CLS_ENERGY}, + {FLD_YW, DEVICE_CLS_ENERGY}, + {FLD_YT, DEVICE_CLS_ENERGY}, + {FLD_UAC, DEVICE_CLS_VOLTAGE}, + {FLD_IAC, DEVICE_CLS_CURRENT}, + {FLD_PAC, DEVICE_CLS_PWR}, + {FLD_F, DEVICE_CLS_FREQ}, + {FLD_T, DEVICE_CLS_TEMP}, + {FLD_EFF, DEVICE_CLS_NONE}, + {FLD_IRR, DEVICE_CLS_NONE} +}; +#define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass)) // indices to calculation functions, defined in hmInverter.h enum {CALC_YT_CH0 = 0, CALC_YD_CH0, CALC_UDC_CH, CALC_PDC_CH0, CALC_EFF_CH0, CALC_IRR_CH}; diff --git a/tools/esp8266/mqtt.h b/tools/esp8266/mqtt.h index 79dbecc5..9b2eff49 100644 --- a/tools/esp8266/mqtt.h +++ b/tools/esp8266/mqtt.h @@ -29,6 +29,7 @@ class mqtt { DPRINTLN(DBG_VERBOSE, F("mqtt.h:setup")); mAddressSet = true; mClient->setServer(broker, port); + mClient->setBufferSize(MQTT_MAX_PACKET_SIZE); mPort = port; snprintf(mUser, MQTT_USER_LEN, "%s", user); @@ -38,14 +39,17 @@ class mqtt { void sendMsg(const char *topic, const char *msg) { //DPRINTLN(DBG_VERBOSE, F("mqtt.h:sendMsg")); - if(mAddressSet) { - char top[64]; - snprintf(top, 64, "%s/%s", mTopic, topic); + char top[64]; + snprintf(top, 64, "%s/%s", mTopic, topic); + sendMsg2(top, msg); + } + void sendMsg2(const char *topic, const char *msg) { + if(mAddressSet) { if(!mClient->connected()) reconnect(); if(mClient->connected()) - mClient->publish(top, msg); + mClient->publish(topic, msg); } } From 9716f1ff8b4e0375e6eb95a702e9dd83b232b48b Mon Sep 17 00:00:00 2001 From: Kai Gerken Date: Sat, 2 Jul 2022 20:25:55 +0200 Subject: [PATCH 2/4] Fix type on field for efficiency --- tools/esp8266/hmDefines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/esp8266/hmDefines.h b/tools/esp8266/hmDefines.h index 3594c2f7..8269f270 100644 --- a/tools/esp8266/hmDefines.h +++ b/tools/esp8266/hmDefines.h @@ -25,7 +25,7 @@ const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%"}; enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT, FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF, FLD_IRR}; const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", - "U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Effiency", "Irradiation"}; + "U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation"}; // mqtt discovery device classes enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP}; From 6e34b4adf9d4ee60c175b3fc891cde62ec95acb9 Mon Sep 17 00:00:00 2001 From: Kai Gerken Date: Sat, 2 Jul 2022 21:16:03 +0200 Subject: [PATCH 3/4] Add 5s on mqtt discovery value expire time. --- tools/esp8266/app.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index 428f3dd2..c739958b 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -154,7 +154,7 @@ void app::setup(uint32_t timeout) { mMqtt.setup(addr, mqttTopic, mqttUser, mqttPwd, mqttPort); mMqttTicker = 0; - + mSerialTicker = 0; if(mqttAddr[0] > 0) { @@ -937,7 +937,7 @@ void app::sendMqttDiscoveryConfig(void) { doc["unit_of_meas"] = iv->getUnit(i); doc["uniq_id"] = String(iv->serial.u64, HEX) + "_" + uniq_id; doc["dev"] = deviceObj; - doc["exp_aft"] = mMqttInterval; + doc["exp_aft"] = mMqttInterval + 5; // add 5 sec if connection is bad or ESP too slow if (devCls != NULL) { doc["dev_cla"] = devCls; } From 3b7eb9c58b8514a236b5f1c28fa345420316337e Mon Sep 17 00:00:00 2001 From: Kai Gerken Date: Wed, 6 Jul 2022 20:55:17 +0200 Subject: [PATCH 4/4] Implement state class for mqtt discovery. --- tools/esp8266/app.cpp | 13 +++++++++++++ tools/esp8266/app.h | 1 + tools/esp8266/hmDefines.h | 32 ++++++++++++++++++-------------- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index c739958b..50120e5f 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -931,6 +931,7 @@ void app::sendMqttDiscoveryConfig(void) { snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->name, iv->assign[i].ch, iv->getFieldName(i)); snprintf(uniq_id, 32, "ch%d_%s", iv->assign[i].ch, iv->getFieldName(i)); const char* devCls = getFieldDeviceClass(iv->assign[i].fieldId); + const char* stateCls = getFieldStateClass(iv->assign[i].fieldId); doc["name"] = name; doc["stat_t"] = stateTopic; @@ -941,6 +942,9 @@ void app::sendMqttDiscoveryConfig(void) { if (devCls != NULL) { doc["dev_cla"] = devCls; } + if (stateCls != NULL) { + doc["stat_cla"] = stateCls; + } serializeJson(doc, buffer); mMqtt.sendMsg2(discoveryTopic, buffer); @@ -963,3 +967,12 @@ const char* app::getFieldDeviceClass(uint8_t fieldId) { } return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : deviceClasses[deviceFieldAssignment[pos].deviceClsId]; } + +const char* app::getFieldStateClass(uint8_t fieldId) { + uint8_t pos = 0; + for(; pos < DEVICE_CLS_ASSIGN_LIST_LEN; pos++) { + if(deviceFieldAssignment[pos].fieldId == fieldId) + break; + } + return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : stateClasses[deviceFieldAssignment[pos].stateClsId]; +} \ No newline at end of file diff --git a/tools/esp8266/app.h b/tools/esp8266/app.h index ef14b9b2..46c6ba67 100644 --- a/tools/esp8266/app.h +++ b/tools/esp8266/app.h @@ -76,6 +76,7 @@ class app : public Main { void updateCrc(void); void sendMqttDiscoveryConfig(void); const char* getFieldDeviceClass(uint8_t fieldId); + const char* getFieldStateClass(uint8_t fieldId); uint64_t Serial2u64(const char *val) { char tmp[3] = {0}; diff --git a/tools/esp8266/hmDefines.h b/tools/esp8266/hmDefines.h index 8269f270..4411a589 100644 --- a/tools/esp8266/hmDefines.h +++ b/tools/esp8266/hmDefines.h @@ -30,24 +30,28 @@ const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", " // mqtt discovery device classes enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP}; const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"}; +enum {STATE_CLS_NONE = 0, STATE_CLS_MEASUREMENT, STATE_CLS_TOTAL_INCREASING}; +const char* const stateClasses[] = {0, "measurement", "total_increasing"}; typedef struct { - uint8_t fieldId; // field id + uint8_t fieldId; // field id uint8_t deviceClsId; // device class + uint8_t stateClsId; // state class } byteAssign_fieldDeviceClass; const byteAssign_fieldDeviceClass deviceFieldAssignment[] = { - {FLD_UDC, DEVICE_CLS_VOLTAGE}, - {FLD_IDC, DEVICE_CLS_CURRENT}, - {FLD_PDC, DEVICE_CLS_PWR}, - {FLD_YD, DEVICE_CLS_ENERGY}, - {FLD_YW, DEVICE_CLS_ENERGY}, - {FLD_YT, DEVICE_CLS_ENERGY}, - {FLD_UAC, DEVICE_CLS_VOLTAGE}, - {FLD_IAC, DEVICE_CLS_CURRENT}, - {FLD_PAC, DEVICE_CLS_PWR}, - {FLD_F, DEVICE_CLS_FREQ}, - {FLD_T, DEVICE_CLS_TEMP}, - {FLD_EFF, DEVICE_CLS_NONE}, - {FLD_IRR, DEVICE_CLS_NONE} + {FLD_UDC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT}, + {FLD_IDC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT}, + {FLD_PDC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT}, + {FLD_YD, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, + {FLD_YW, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, + {FLD_YT, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING}, + {FLD_UAC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT}, + {FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT}, + {FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT}, + {FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE}, + {FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT}, + {FLD_PCT, DEVICE_CLS_NONE, STATE_CLS_NONE}, + {FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE}, + {FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE} }; #define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass))