diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino index c84d9635..6f00b570 100644 --- a/src/AmsToMqttBridge.ino +++ b/src/AmsToMqttBridge.ino @@ -32,6 +32,7 @@ ADC_MODE(ADC_VCC); #include "mqtt/JsonMqttHandler.h" #include "mqtt/RawMqttHandler.h" #include "mqtt/DomoticzMqttHandler.h" +#include "mqtt/HomeAssistantMqttHandler.h" #include "Uptime.h" @@ -947,6 +948,9 @@ void MQTT_connect() { config.getDomoticzConfig(domo); mqttHandler = new DomoticzMqttHandler(mqtt, domo); break; + case 4: + mqttHandler = new HomeAssistantMqttHandler(mqtt, mqttConfig.clientId, mqttConfig.publishTopic, &hw); + break; } if(mqttConfig.ssl) { diff --git a/src/mqtt/HomeAssistantMqttHandler.cpp b/src/mqtt/HomeAssistantMqttHandler.cpp new file mode 100644 index 00000000..fc8a1c8f --- /dev/null +++ b/src/mqtt/HomeAssistantMqttHandler.cpp @@ -0,0 +1,272 @@ +#include "HomeAssistantMqttHandler.h" +#include "hexutils.h" +#include "Uptime.h" +#include "web/root/json1_json.h" +#include "web/root/json2_json.h" +#include "web/root/json3_json.h" +#include "web/root/json3pf_json.h" +#include "web/root/jsonsys_json.h" +#include "web/root/jsonprices_json.h" + +bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState) { + if(topic.isEmpty() || !mqtt->connected()) + return false; + + if(data->getListType() == 1) { + char json[192]; + snprintf_P(json, sizeof(json), JSON1_JSON, + WiFi.macAddress().c_str(), + clientId.c_str(), + (uint32_t) (millis64()/1000), + data->getPackageTimestamp(), + hw->getVcc(), + hw->getWifiRssi(), + hw->getTemperature(), + data->getActiveImportPower() + ); + return mqtt->publish(topic, json); + } else if(data->getListType() == 2) { + char json[384]; + snprintf_P(json, sizeof(json), JSON2_JSON, + WiFi.macAddress().c_str(), + clientId.c_str(), + (uint32_t) (millis64()/1000), + data->getPackageTimestamp(), + hw->getVcc(), + hw->getWifiRssi(), + hw->getTemperature(), + data->getListId().c_str(), + data->getMeterId().c_str(), + data->getMeterModel().c_str(), + data->getActiveImportPower(), + data->getReactiveImportPower(), + data->getActiveExportPower(), + data->getReactiveExportPower(), + data->getL1Current(), + data->getL2Current(), + data->getL3Current(), + data->getL1Voltage(), + data->getL2Voltage(), + data->getL3Voltage() + ); + return mqtt->publish(topic, json); + } else if(data->getListType() == 3) { + if(data->getPowerFactor() == 0) { + char json[512]; + snprintf_P(json, sizeof(json), JSON3_JSON, + WiFi.macAddress().c_str(), + clientId.c_str(), + (uint32_t) (millis64()/1000), + data->getPackageTimestamp(), + hw->getVcc(), + hw->getWifiRssi(), + hw->getTemperature(), + data->getListId().c_str(), + data->getMeterId().c_str(), + data->getMeterModel().c_str(), + data->getActiveImportPower(), + data->getReactiveImportPower(), + data->getActiveExportPower(), + data->getReactiveExportPower(), + data->getL1Current(), + data->getL2Current(), + data->getL3Current(), + data->getL1Voltage(), + data->getL2Voltage(), + data->getL3Voltage(), + data->getActiveImportCounter(), + data->getActiveExportCounter(), + data->getReactiveImportCounter(), + data->getReactiveExportCounter(), + data->getMeterTimestamp() + ); + return mqtt->publish(topic, json); + } else { + char json[768]; + snprintf_P(json, sizeof(json), JSON3PF_JSON, + WiFi.macAddress().c_str(), + clientId.c_str(), + (uint32_t) (millis64()/1000), + data->getPackageTimestamp(), + hw->getVcc(), + hw->getWifiRssi(), + hw->getTemperature(), + data->getListId().c_str(), + data->getMeterId().c_str(), + data->getMeterModel().c_str(), + data->getActiveImportPower(), + data->getReactiveImportPower(), + data->getActiveExportPower(), + data->getReactiveExportPower(), + data->getL1Current(), + data->getL2Current(), + data->getL3Current(), + data->getL1Voltage(), + data->getL2Voltage(), + data->getL3Voltage(), + data->getPowerFactor(), + data->getL1PowerFactor(), + data->getL2PowerFactor(), + data->getL3PowerFactor(), + data->getActiveImportCounter(), + data->getActiveExportCounter(), + data->getReactiveImportCounter(), + data->getReactiveExportCounter(), + data->getMeterTimestamp() + ); + return mqtt->publish(topic, json); + } + } + return false; +} + +bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) { + int count = hw->getTempSensorCount(); + if(count == 0) + return false; + + int size = 32 + (count * 26); + + char buf[size]; + snprintf(buf, 24, "{\"temperatures\":{"); + + for(int i = 0; i < count; i++) { + TempSensorData* data = hw->getTempSensorData(i); + if(data != NULL) { + char* pos = buf+strlen(buf); + snprintf(pos, 26, "\"%s\":%.2f,", + toHex(data->address, 8).c_str(), + data->lastRead + ); + data->changed = false; + delay(1); + } + } + char* pos = buf+strlen(buf); + snprintf(count == 0 ? pos : pos-1, 8, "}}"); + return mqtt->publish(topic, buf); +} + +bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) { + if(topic.isEmpty() || !mqtt->connected()) + return false; + if(strlen(eapi->getToken()) == 0) + return false; + + time_t now = time(nullptr); + + float min1hr, min3hr, min6hr; + int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1; + float min = INT16_MAX, max = INT16_MIN; + float values[24] = {0}; + for(uint8_t i = 0; i < 24; i++) { + float val = eapi->getValueForHour(now, i); + values[i] = val; + + if(val == ENTSOE_NO_VALUE) break; + + if(val < min) min = val; + if(val > max) max = val; + + if(min1hrIdx == -1 || min1hr > val) { + min1hr = val; + min1hrIdx = i; + } + + if(i >= 2) { + i -= 2; + float val1 = values[i++]; + float val2 = values[i++]; + float val3 = val; + if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue; + float val3hr = val1+val2+val3; + if(min3hrIdx == -1 || min3hr > val3hr) { + min3hr = val3hr; + min3hrIdx = i-2; + } + } + + if(i >= 5) { + i -= 5; + float val1 = values[i++]; + float val2 = values[i++]; + float val3 = values[i++]; + float val4 = values[i++]; + float val5 = values[i++]; + float val6 = val; + if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue; + float val6hr = val1+val2+val3+val4+val5+val6; + if(min6hrIdx == -1 || min6hr > val6hr) { + min6hr = val6hr; + min6hrIdx = i-5; + } + } + + } + + char ts1hr[21]; + if(min1hrIdx > -1) { + time_t ts = now + (SECS_PER_HOUR * min1hrIdx); + //Serial.printf("1hr: %d %lu\n", min1hrIdx, ts); + tmElements_t tm; + breakTime(ts, tm); + sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); + } + char ts3hr[21]; + if(min3hrIdx > -1) { + time_t ts = now + (SECS_PER_HOUR * min3hrIdx); + //Serial.printf("3hr: %d %lu\n", min3hrIdx, ts); + tmElements_t tm; + breakTime(ts, tm); + sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); + } + char ts6hr[21]; + if(min6hrIdx > -1) { + time_t ts = now + (SECS_PER_HOUR * min6hrIdx); + //Serial.printf("6hr: %d %lu\n", min6hrIdx, ts); + tmElements_t tm; + breakTime(ts, tm); + sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour); + } + + char json[384]; + snprintf_P(json, sizeof(json), JSONPRICES_JSON, + WiFi.macAddress().c_str(), + values[0], + values[1], + values[2], + values[3], + values[4], + values[5], + values[6], + values[7], + values[8], + values[9], + values[10], + values[11], + min == INT16_MAX ? 0.0 : min, + max == INT16_MIN ? 0.0 : max, + ts1hr, + ts3hr, + ts6hr + ); + return mqtt->publish(topic, json); +} + +bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) { + if(init || topic.isEmpty() || !mqtt->connected()) + return false; + if(!topic.endsWith("/")) topic += "/"; + + char json[192]; + snprintf_P(json, sizeof(json), JSONSYS_JSON, + WiFi.macAddress().c_str(), + clientId.c_str(), + (uint32_t) (millis64()/1000), + hw->getVcc(), + hw->getWifiRssi(), + hw->getTemperature() + ); + init = mqtt->publish(topic, json); + return init; +} diff --git a/src/mqtt/HomeAssistantMqttHandler.h b/src/mqtt/HomeAssistantMqttHandler.h new file mode 100644 index 00000000..14c652b2 --- /dev/null +++ b/src/mqtt/HomeAssistantMqttHandler.h @@ -0,0 +1,24 @@ +#ifndef _HOMEASSISTANTMQTTHANDLER_H +#define _HOMEASSISTANTMQTTHANDLER_H + +#include "AmsMqttHandler.h" + +class HomeAssistantMqttHandler : public AmsMqttHandler { +public: + HomeAssistantMqttHandler(MQTTClient* mqtt, const char* clientId, const char* topic, HwTools* hw) : AmsMqttHandler(mqtt) { + this->clientId = clientId; + this->topic = String(topic); + this->hw = hw; + }; + bool publish(AmsData* data, AmsData* previousState); + bool publishTemperatures(AmsConfiguration*, HwTools*); + bool publishPrices(EntsoeApi*); + bool publishSystem(HwTools*); + +private: + String clientId; + String topic; + HwTools* hw; + bool init = false; +}; +#endif diff --git a/web/mqtt.html b/web/mqtt.html index 9fefb1d7..ce128e01 100644 --- a/web/mqtt.html +++ b/web/mqtt.html @@ -62,6 +62,7 @@ +