|
@ -34,6 +34,12 @@ struct alarm_t { |
|
|
alarm_t(uint16_t c, uint32_t s, uint32_t e) : code(c), start(s), end(e) {} |
|
|
alarm_t(uint16_t c, uint32_t s, uint32_t e) : code(c), start(s), end(e) {} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
typedef struct { |
|
|
|
|
|
bool running; |
|
|
|
|
|
uint8_t lastIvId; |
|
|
|
|
|
uint8_t sub; |
|
|
|
|
|
} discovery_t; |
|
|
|
|
|
|
|
|
template<class HMSYSTEM> |
|
|
template<class HMSYSTEM> |
|
|
class PubMqtt { |
|
|
class PubMqtt { |
|
|
public: |
|
|
public: |
|
@ -55,6 +61,8 @@ class PubMqtt { |
|
|
mUtcTimestamp = utcTs; |
|
|
mUtcTimestamp = utcTs; |
|
|
mIntervalTimeout = 1; |
|
|
mIntervalTimeout = 1; |
|
|
|
|
|
|
|
|
|
|
|
mDiscovery.running = false; |
|
|
|
|
|
|
|
|
snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic); |
|
|
snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic); |
|
|
|
|
|
|
|
|
if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) |
|
|
if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) |
|
@ -82,6 +90,9 @@ class PubMqtt { |
|
|
mClient.loop(); |
|
|
mClient.loop(); |
|
|
yield(); |
|
|
yield(); |
|
|
#endif |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
if(mDiscovery.running) |
|
|
|
|
|
discoveryConfigLoop(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -210,92 +221,9 @@ class PubMqtt { |
|
|
|
|
|
|
|
|
void sendDiscoveryConfig(void) { |
|
|
void sendDiscoveryConfig(void) { |
|
|
DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig")); |
|
|
DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig")); |
|
|
|
|
|
mDiscovery.running = true; |
|
|
char topic[64], name[32], uniq_id[32]; |
|
|
mDiscovery.lastIvId = 0; |
|
|
DynamicJsonDocument doc(256); |
|
|
mDiscovery.sub = 0; |
|
|
|
|
|
|
|
|
uint8_t fldTotal[4] = {FLD_PAC, FLD_YT, FLD_YD, FLD_PDC}; |
|
|
|
|
|
const char* unitTotal[4] = {"W", "kWh", "Wh", "W"}; |
|
|
|
|
|
|
|
|
|
|
|
String node_mac = WiFi.macAddress().substring(12,14)+ WiFi.macAddress().substring(15,17); |
|
|
|
|
|
String node_id = "AHOY_DTU_" + node_mac; |
|
|
|
|
|
bool total = false; |
|
|
|
|
|
|
|
|
|
|
|
for (uint8_t id = 0; id < mSys->getNumInverters() ; id++) { |
|
|
|
|
|
doc.clear(); |
|
|
|
|
|
|
|
|
|
|
|
if (total) // total become true at iv = NULL next cycle
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
Inverter<> *iv = mSys->getInverterByPos(id); |
|
|
|
|
|
if (NULL == iv) |
|
|
|
|
|
total = true; |
|
|
|
|
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); |
|
|
|
|
|
|
|
|
|
|
|
if (!total) { |
|
|
|
|
|
doc[F("name")] = iv->config->name; |
|
|
|
|
|
doc[F("ids")] = String(iv->config->serial.u64, HEX); |
|
|
|
|
|
doc[F("mdl")] = iv->config->name; |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
doc[F("name")] = node_id; |
|
|
|
|
|
doc[F("ids")] = node_id; |
|
|
|
|
|
doc[F("mdl")] = node_id; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
doc[F("cu")] = F("http://") + String(WiFi.localIP().toString()); |
|
|
|
|
|
doc[F("mf")] = F("Hoymiles"); |
|
|
|
|
|
JsonObject deviceObj = doc.as<JsonObject>(); // deviceObj is only pointer!?
|
|
|
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < ((!total) ? (rec->length) : (4) ) ; i++) { |
|
|
|
|
|
const char *devCls, *stateCls; |
|
|
|
|
|
if (!total) { |
|
|
|
|
|
if (rec->assign[i].ch == CH0) |
|
|
|
|
|
snprintf(name, 32, "%s %s", iv->config->name, iv->getFieldName(i, rec)); |
|
|
|
|
|
else |
|
|
|
|
|
snprintf(name, 32, "%s CH%d %s", iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); |
|
|
|
|
|
snprintf(topic, 64, "/ch%d/%s", rec->assign[i].ch, iv->getFieldName(i, rec)); |
|
|
|
|
|
snprintf(uniq_id, 32, "ch%d_%s", rec->assign[i].ch, iv->getFieldName(i, rec)); |
|
|
|
|
|
|
|
|
|
|
|
devCls = getFieldDeviceClass(rec->assign[i].fieldId); |
|
|
|
|
|
stateCls = getFieldStateClass(rec->assign[i].fieldId); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
else { // total values
|
|
|
|
|
|
snprintf(name, 32, "Total %s", fields[fldTotal[i]]); |
|
|
|
|
|
snprintf(topic, 64, "/%s", fields[fldTotal[i]]); |
|
|
|
|
|
snprintf(uniq_id, 32, "total_%s", fields[fldTotal[i]]); |
|
|
|
|
|
devCls = getFieldDeviceClass(fldTotal[i]); |
|
|
|
|
|
stateCls = getFieldStateClass(fldTotal[i]); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DynamicJsonDocument doc2(512); |
|
|
|
|
|
doc2[F("name")] = name; |
|
|
|
|
|
doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + ((!total) ? String(iv->config->name) : "total" ) + String(topic); |
|
|
|
|
|
doc2[F("unit_of_meas")] = ((!total) ? (iv->getUnit(i,rec)) : (unitTotal[i])); |
|
|
|
|
|
doc2[F("uniq_id")] = ((!total) ? (String(iv->config->serial.u64, HEX)) : (node_id)) + "_" + uniq_id; |
|
|
|
|
|
doc2[F("dev")] = deviceObj; |
|
|
|
|
|
if (!(String(stateCls) == String("total_increasing"))) |
|
|
|
|
|
doc2[F("exp_aft")] = MQTT_INTERVAL + 5; // add 5 sec if connection is bad or ESP too slow @TODO: stimmt das wirklich als expire!?
|
|
|
|
|
|
if (devCls != NULL) |
|
|
|
|
|
doc2[F("dev_cla")] = String(devCls); |
|
|
|
|
|
if (stateCls != NULL) |
|
|
|
|
|
doc2[F("stat_cla")] = String(stateCls); |
|
|
|
|
|
|
|
|
|
|
|
if (!total) |
|
|
|
|
|
snprintf(topic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); |
|
|
|
|
|
else // total values
|
|
|
|
|
|
snprintf(topic, 64, "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(),fields[fldTotal[i]]); |
|
|
|
|
|
size_t size = measureJson(doc2) + 1; |
|
|
|
|
|
char *buf = new char[size]; |
|
|
|
|
|
memset(buf, 0, size); |
|
|
|
|
|
serializeJson(doc2, buf, size); |
|
|
|
|
|
publish(topic, buf, true, false); |
|
|
|
|
|
delete[] buf; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
yield(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void setPowerLimitAck(Inverter<> *iv) { |
|
|
void setPowerLimitAck(Inverter<> *iv) { |
|
@ -415,6 +343,95 @@ class PubMqtt { |
|
|
mRxCnt++; |
|
|
mRxCnt++; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void discoveryConfigLoop(void) { |
|
|
|
|
|
char topic[64], name[32], uniq_id[32], buf[350]; |
|
|
|
|
|
DynamicJsonDocument doc(256); |
|
|
|
|
|
|
|
|
|
|
|
uint8_t fldTotal[4] = {FLD_PAC, FLD_YT, FLD_YD, FLD_PDC}; |
|
|
|
|
|
const char* unitTotal[4] = {"W", "kWh", "Wh", "W"}; |
|
|
|
|
|
|
|
|
|
|
|
String node_mac = WiFi.macAddress().substring(12,14)+ WiFi.macAddress().substring(15,17); |
|
|
|
|
|
String node_id = "AHOY_DTU_" + node_mac; |
|
|
|
|
|
bool total = (mDiscovery.lastIvId == MAX_NUM_INVERTERS); |
|
|
|
|
|
|
|
|
|
|
|
Inverter<> *iv = mSys->getInverterByPos(mDiscovery.lastIvId); |
|
|
|
|
|
record_t<> *rec; |
|
|
|
|
|
if (NULL != iv) |
|
|
|
|
|
rec = iv->getRecordStruct(RealTimeRunData_Debug); |
|
|
|
|
|
|
|
|
|
|
|
if ((NULL != iv) || total) { |
|
|
|
|
|
if (!total) { |
|
|
|
|
|
doc[F("name")] = iv->config->name; |
|
|
|
|
|
doc[F("ids")] = String(iv->config->serial.u64, HEX); |
|
|
|
|
|
doc[F("mdl")] = iv->config->name; |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
doc[F("name")] = node_id; |
|
|
|
|
|
doc[F("ids")] = node_id; |
|
|
|
|
|
doc[F("mdl")] = node_id; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
doc[F("cu")] = F("http://") + String(WiFi.localIP().toString()); |
|
|
|
|
|
doc[F("mf")] = F("Hoymiles"); |
|
|
|
|
|
JsonObject deviceObj = doc.as<JsonObject>(); // deviceObj is only pointer!?
|
|
|
|
|
|
|
|
|
|
|
|
const char *devCls, *stateCls; |
|
|
|
|
|
if (!total) { |
|
|
|
|
|
if (rec->assign[mDiscovery.sub].ch == CH0) |
|
|
|
|
|
snprintf(name, 32, "%s %s", iv->config->name, iv->getFieldName(mDiscovery.sub, rec)); |
|
|
|
|
|
else |
|
|
|
|
|
snprintf(name, 32, "%s CH%d %s", iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); |
|
|
|
|
|
snprintf(topic, 64, "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); |
|
|
|
|
|
snprintf(uniq_id, 32, "ch%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); |
|
|
|
|
|
|
|
|
|
|
|
devCls = getFieldDeviceClass(rec->assign[mDiscovery.sub].fieldId); |
|
|
|
|
|
stateCls = getFieldStateClass(rec->assign[mDiscovery.sub].fieldId); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
else { // total values
|
|
|
|
|
|
snprintf(name, 32, "Total %s", fields[fldTotal[mDiscovery.sub]]); |
|
|
|
|
|
snprintf(topic, 64, "/%s", fields[fldTotal[mDiscovery.sub]]); |
|
|
|
|
|
snprintf(uniq_id, 32, "total_%s", fields[fldTotal[mDiscovery.sub]]); |
|
|
|
|
|
devCls = getFieldDeviceClass(fldTotal[mDiscovery.sub]); |
|
|
|
|
|
stateCls = getFieldStateClass(fldTotal[mDiscovery.sub]); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DynamicJsonDocument doc2(512); |
|
|
|
|
|
doc2[F("name")] = name; |
|
|
|
|
|
doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + ((!total) ? String(iv->config->name) : "total" ) + String(topic); |
|
|
|
|
|
doc2[F("unit_of_meas")] = ((!total) ? (iv->getUnit(mDiscovery.sub, rec)) : (unitTotal[mDiscovery.sub])); |
|
|
|
|
|
doc2[F("uniq_id")] = ((!total) ? (String(iv->config->serial.u64, HEX)) : (node_id)) + "_" + uniq_id; |
|
|
|
|
|
doc2[F("dev")] = deviceObj; |
|
|
|
|
|
if (!(String(stateCls) == String("total_increasing"))) |
|
|
|
|
|
doc2[F("exp_aft")] = MQTT_INTERVAL + 5; // add 5 sec if connection is bad or ESP too slow @TODO: stimmt das wirklich als expire!?
|
|
|
|
|
|
if (devCls != NULL) |
|
|
|
|
|
doc2[F("dev_cla")] = String(devCls); |
|
|
|
|
|
if (stateCls != NULL) |
|
|
|
|
|
doc2[F("stat_cla")] = String(stateCls); |
|
|
|
|
|
|
|
|
|
|
|
if (!total) |
|
|
|
|
|
snprintf(topic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); |
|
|
|
|
|
else // total values
|
|
|
|
|
|
snprintf(topic, 64, "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(),fields[fldTotal[mDiscovery.sub]]); |
|
|
|
|
|
size_t size = measureJson(doc2) + 1; |
|
|
|
|
|
memset(buf, 0, size); |
|
|
|
|
|
serializeJson(doc2, buf, size); |
|
|
|
|
|
publish(topic, buf, true, false); |
|
|
|
|
|
|
|
|
|
|
|
if(++mDiscovery.sub == ((!total) ? (rec->length) : 4)) { |
|
|
|
|
|
mDiscovery.sub = 0; |
|
|
|
|
|
if(++mDiscovery.lastIvId == (MAX_NUM_INVERTERS + 1)) |
|
|
|
|
|
mDiscovery.running = false; |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
mDiscovery.sub = 0; |
|
|
|
|
|
if(++mDiscovery.lastIvId == (MAX_NUM_INVERTERS + 1)) |
|
|
|
|
|
mDiscovery.running = false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
yield(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const char *getFieldDeviceClass(uint8_t fieldId) { |
|
|
const char *getFieldDeviceClass(uint8_t fieldId) { |
|
|
uint8_t pos = 0; |
|
|
uint8_t pos = 0; |
|
|
for (; pos < DEVICE_CLS_ASSIGN_LIST_LEN; pos++) { |
|
|
for (; pos < DEVICE_CLS_ASSIGN_LIST_LEN; pos++) { |
|
@ -643,6 +660,8 @@ class PubMqtt { |
|
|
char mClientId[24]; // number of chars is limited to 23 up to v3.1 of MQTT
|
|
|
char mClientId[24]; // number of chars is limited to 23 up to v3.1 of MQTT
|
|
|
// global buffer for mqtt topic. Used when publishing mqtt messages.
|
|
|
// global buffer for mqtt topic. Used when publishing mqtt messages.
|
|
|
char mTopic[MQTT_TOPIC_LEN + 32 + MAX_NAME_LENGTH + 1]; |
|
|
char mTopic[MQTT_TOPIC_LEN + 32 + MAX_NAME_LENGTH + 1]; |
|
|
|
|
|
|
|
|
|
|
|
discovery_t mDiscovery; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
#endif /*__PUB_MQTT_H__*/ |
|
|
#endif /*__PUB_MQTT_H__*/ |
|
|