#include "HomeAssistantMqttHandler.h" #include "hexutils.h" #include "Uptime.h" #include "web/root/ha1_json.h" #include "web/root/ha2_json.h" #include "web/root/ha3_json.h" #include "web/root/jsonsys_json.h" #include "web/root/jsonprices_json.h" #include "web/root/hadiscover1_json.h" #include "web/root/hadiscover2_json.h" bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState) { if(topic.isEmpty() || !mqtt->connected()) return false; listType = data->getListType(); // for discovery stuff in publishSystem() if(data->getListType() == 3) { // publish energy counts char json[256]; snprintf_P(json, sizeof(json), HA2_JSON, data->getActiveImportCounter(), data->getActiveExportCounter(), data->getReactiveImportCounter(), data->getReactiveExportCounter(), data->getMeterTimestamp() ); mqtt->publish(topic + "/energy", json); } if(data->getListType() == 1) { // publish power counts char json[192]; snprintf_P(json, sizeof(json), HA1_JSON, data->getActiveImportPower() ); return mqtt->publish(topic + "/power", json); } else if(data->getListType() == 2 || data->getListType() == 3) { // publish power counts and volts/amps char json[768]; snprintf_P(json, sizeof(json), HA3_JSON, 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() == 0 ? 1 : data->getPowerFactor(), data->getPowerFactor() == 0 ? 1 : data->getL1PowerFactor(), data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(), data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor() ); return mqtt->publish(topic + "/power", 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 + "/temperatures", 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 + "/prices", json); } bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) { if(topic.isEmpty() || !mqtt->connected()){ sequence = 0; return false; } if(sequence % 3 == 0){ 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() ); mqtt->publish(topic + "/state", json); } if(sequence % 60 == 1 && listType > 1){ // every 60 ams message, publish mqtt discovery. TODO: publish once with retain char json[512]; String haTopic = "homeassistant/sensor/"; // home-assistant discovery topic String haUID = "ams-3a08"; // unit identity (wifi hostname) int sensors = 17; String topics[sensors] = {"/state", "/state", "/state", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/energy", "/energy", "/energy", "/energy"}; String names[sensors] = {"Status", "Supply volt", "Temperature", "Active import", "Reactive import", "Active export", "Reactive export", "L1 current", "L2 current", "L3 current", "L1 voltage", "L2 voltage", "L3 voltage", "Accumulated active import", "Accumulated active export", "Accumulated reactive import", "Accumulated reactive export"}; String params[sensors] = {"rssi", "vcc", "temp", "P", "Q", "PO", "QO", "I1", "I2", "I3", "U1", "U2", "U3", "tPI", "tPO", "tQI", "tQO"}; String uom[sensors] = {"dBm", "V", "C", "W", "W", "W", "W", "A", "A", "A", "V", "V", "V", "kWh", "kWh", "kWh", "kWh"}; String devcl[sensors] = {"signal_strength", "voltage", "temperature", "power", "power", "power", "power", "current", "current", "current", "voltage", "voltage", "voltage", "energy", "energy", "energy", "energy"}; String stacl[sensors] = {"", "", "", "measurement", "measurement", "measurement", "measurement", "", "", "", "", "", "", "total_increasing", "total_increasing", "total_increasing", "total_increasing"}; //String category[sensors] = {"Diagnostic", "Diagnostic", "Diagnostic", "Power", "Power", "Power", "Power", "Voltage", "Voltage", "Voltage", "Current", "Current", "Current", "Energy", "Energy", "Energy", "Energy"}; String haName = "AMS reader"; String haModel = "ESP32"; String haVersion = "2.0.0"; String haManuf = "AmsToMqttBridge"; String haUrl = "http://" + haUID + ".local/"; for(int i=0;i 0) { // TODO: reduce to single JSON, state_class: null (witout quotation). or make it some extra optional string that us appended snprintf_P(json, sizeof(json), HADISCOVER2_JSON, names[i].c_str(), // name (topic + topics[i]).c_str(), // state_topic (haUID + "_" + params[i]).c_str(), // unique_id (haUID + "_" + params[i]).c_str(), // object_id uom[i].c_str(), // unit_of_measurement params[i].c_str(), // value_template devcl[i].c_str(), // device_class haUID.c_str(), // dev ids haName.c_str(), // name haModel.c_str(), // model haVersion.c_str(), // fw version haManuf.c_str(), // manufacturer haUrl.c_str(), // configuration_url stacl[i].c_str() // state_class ); } else { snprintf_P(json, sizeof(json), HADISCOVER1_JSON, names[i].c_str(), // name (topic + topics[i]).c_str(), // state_topic (haUID + "_" + params[i]).c_str(), // unique_id (haUID + "_" + params[i]).c_str(), // object_id uom[i].c_str(), // unit_of_measurement params[i].c_str(), // value_template devcl[i].c_str(), // device_class haUID.c_str(), // dev ids haName.c_str(), // name haModel.c_str(), // model haVersion.c_str(), // fw version haManuf.c_str(), // manufacturer haUrl.c_str() // configuration_url ); } mqtt->publish(haTopic + haUID + "_" + params[i] + "/config", json); } } if(listType>0) sequence++; return true; }