Merge branch 'pr-172' into dev-v2.1.0

This commit is contained in:
Gunnar Skjold
2022-02-20 11:17:33 +01:00
11 changed files with 400 additions and 1 deletions

View File

@@ -41,6 +41,7 @@ ADC_MODE(ADC_VCC);
#include "mqtt/JsonMqttHandler.h"
#include "mqtt/RawMqttHandler.h"
#include "mqtt/DomoticzMqttHandler.h"
#include "mqtt/HomeAssistantMqttHandler.h"
#include "Uptime.h"
@@ -1160,6 +1161,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) {

View File

@@ -0,0 +1,256 @@
#include "HomeAssistantMqttHandler.h"
#include "hexutils.h"
#include "Uptime.h"
#include "version.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
snprintf_P(json, BufferSize, HA2_JSON,
data->getActiveImportCounter(),
data->getActiveExportCounter(),
data->getReactiveImportCounter(),
data->getReactiveExportCounter(),
data->getMeterTimestamp()
);
mqtt->publish(topic + "/energy", json);
}
if(data->getListType() == 1) { // publish power counts
snprintf_P(json, BufferSize, HA1_JSON,
data->getActiveImportPower()
);
return mqtt->publish(topic + "/power", json);
} else if(data->getListType() >= 2) { // publish power counts and volts/amps
snprintf_P(json, BufferSize, 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);
}
snprintf_P(json, BufferSize, 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){
snprintf_P(json, BufferSize, 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(!autodiscoverInit) {
#if defined(ESP8266)
String haUID = WiFi.hostname();
#elif defined(ESP32)
String haUID = WiFi.getHostname();
#endif
String haUrl = "http://" + haUID + ".local/";
for(int i=0;i<sensors;i++){
if(stacl[i].length() > 0) { // TODO: reduce to single JSON, state_class: null (witout quotation). or make it some extra optional string that us appended
snprintf_P(json, BufferSize, 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
VERSION, // fw version
haManuf.c_str(), // manufacturer
haUrl.c_str(), // configuration_url
stacl[i].c_str() // state_class
);
} else {
snprintf_P(json, BufferSize, 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
VERSION, // fw version
haManuf.c_str(), // manufacturer
haUrl.c_str() // configuration_url
);
}
mqtt->publish(haTopic + haUID + "_" + params[i] + "/config", json, true, 0);
}
autodiscoverInit = true;
}
if(listType>0) sequence++;
return true;
}

View File

@@ -0,0 +1,50 @@
#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;
this->json = (char*) malloc(BufferSize);
};
bool publish(AmsData* data, AmsData* previousState);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*);
static const uint16_t BufferSize = 768;
private:
static const uint8_t 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 haTopic = "homeassistant/sensor/";
String haName = "AMS reader";
#if defined(ESP32)
String haModel = "ESP32";
#elif defined(ESP8266)
String haModel = "ESP8266";
#endif
String haManuf = "AmsToMqttBridge";
bool autodiscoverInit = false;
String clientId;
String topic;
HwTools* hw;
uint8_t sequence = 0, listType = 0;
char* json;
};
#endif

View File

@@ -559,7 +559,7 @@ void AmsWebServer::configMqttHtml() {
html.replace("{u}", mqtt.username);
html.replace("{pw}", mqtt.password);
html.replace("{f}", String(mqtt.payloadFormat));
for(int i = 0; i<4; i++) {
for(int i = 0; i<5; i++) {
html.replace("{f" + String(i) + "}", mqtt.payloadFormat == i ? "selected" : "");
}
html.replace("{f255}", mqtt.payloadFormat == 255 ? "selected" : "");