mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-01-13 15:37:03 +00:00
Refactored MQTT payload handling into separate classes
This commit is contained in:
parent
53573184f3
commit
33070af111
@ -4,7 +4,7 @@ extra_configs = platformio-user.ini
|
||||
|
||||
[common]
|
||||
framework = arduino
|
||||
lib_deps = file://lib/HanReader, file://lib/Timezone, ArduinoJson@6.14.1, MQTT@2.4.7, DallasTemperature@3.8.1, EspSoftwareSerial@6.7.1, RemoteDebug@3.0.5, Time@1.6
|
||||
lib_deps = file://lib/HanReader, file://lib/Timezone, MQTT@2.4.7, DallasTemperature@3.8.1, EspSoftwareSerial@6.7.1, RemoteDebug@3.0.5, Time@1.6
|
||||
|
||||
[env:esp8266]
|
||||
platform = espressif8266@2.5.1
|
||||
|
||||
@ -105,6 +105,7 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) {
|
||||
mqttChanged |= strcmp(config.subscribeTopic, existing.subscribeTopic) != 0;
|
||||
mqttChanged |= strcmp(config.username, existing.username) != 0;
|
||||
mqttChanged |= strcmp(config.password, existing.password) != 0;
|
||||
mqttChanged |= config.payloadFormat != existing.payloadFormat;
|
||||
mqttChanged |= config.ssl != existing.ssl;
|
||||
} else {
|
||||
mqttChanged = true;
|
||||
@ -254,6 +255,7 @@ bool AmsConfiguration::setDomoticzConfig(DomoticzConfig& config) {
|
||||
} else {
|
||||
domoChanged = true;
|
||||
}
|
||||
mqttChanged = domoChanged;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(CONFIG_DOMOTICZ_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
#include "AmsToMqttBridge.h"
|
||||
#include "AmsStorage.h"
|
||||
#define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e9
|
||||
#include <ArduinoJson.h>
|
||||
#include <MQTT.h>
|
||||
#include <DNSServer.h>
|
||||
#include <lwip/apps/sntp.h>
|
||||
@ -34,7 +33,11 @@ ADC_MODE(ADC_VCC);
|
||||
#include "web/AmsWebServer.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "HanReader.h"
|
||||
#include "HanToJson.h"
|
||||
|
||||
#include "mqtt/AmsMqttHandler.h"
|
||||
#include "mqtt/JsonMqttHandler.h"
|
||||
#include "mqtt/RawMqttHandler.h"
|
||||
#include "mqtt/DomoticzMqttHandler.h"
|
||||
|
||||
#include "Aidon.h"
|
||||
#include "Kaifa.h"
|
||||
@ -61,6 +64,7 @@ Timezone* tz;
|
||||
AmsWebServer ws(&Debug, &hw, &eapi);
|
||||
|
||||
MQTTClient mqtt(512);
|
||||
AmsMqttHandler* mqttHandler = NULL;
|
||||
|
||||
HanReader hanReader;
|
||||
|
||||
@ -68,10 +72,7 @@ Stream *hanSerial;
|
||||
|
||||
GpioConfig gpioConfig;
|
||||
MeterConfig meterConfig;
|
||||
DomoticzConfig* domoConfig = NULL;
|
||||
float energy = 0.0;
|
||||
bool mqttEnabled = false;
|
||||
String clientId = "ams-reader";
|
||||
uint8_t payloadFormat = 0;
|
||||
String topic = "ams";
|
||||
AmsData meterState;
|
||||
@ -290,18 +291,6 @@ unsigned long lastSuccessfulRead = 0;
|
||||
unsigned long lastErrorBlink = 0;
|
||||
int lastError = 0;
|
||||
|
||||
String toHex(uint8_t* in) {
|
||||
String hex;
|
||||
for(int i = 0; i < sizeof(in)*2; i++) {
|
||||
if(in[i] < 0x10) {
|
||||
hex += '0';
|
||||
}
|
||||
hex += String(in[i], HEX);
|
||||
}
|
||||
hex.toUpperCase();
|
||||
return hex;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Debug.handle();
|
||||
unsigned long now = millis();
|
||||
@ -333,38 +322,8 @@ void loop() {
|
||||
hw.updateTemperatures();
|
||||
lastTemperatureRead = now;
|
||||
|
||||
uint8_t c = hw.getTempSensorCount();
|
||||
|
||||
if(WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt.connected() && !topic.isEmpty()) {
|
||||
if(payloadFormat == 1 || payloadFormat == 2) {
|
||||
for(int i = 0; i < c; i++) {
|
||||
TempSensorData* data = hw.getTempSensorData(i);
|
||||
if(data->lastValidRead > -85) {
|
||||
if(data->changed || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/temperature/" + toHex(data->address), String(data->lastValidRead, 2));
|
||||
data->changed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(payloadFormat == 0) {
|
||||
StaticJsonDocument<512> json;
|
||||
JsonObject temps = json.createNestedObject("temperatures");
|
||||
for(int i = 0; i < c; i++) {
|
||||
TempSensorData* data = hw.getTempSensorData(i);
|
||||
if(data->lastValidRead > -85) {
|
||||
TempSensorConfig* conf = config.getTempSensorConfig(data->address);
|
||||
JsonObject obj = temps.createNestedObject(toHex(data->address));
|
||||
if(conf != NULL) {
|
||||
obj["name"] = conf->name;
|
||||
}
|
||||
obj["value"] = serialized(String(data->lastValidRead, 2));
|
||||
}
|
||||
data->changed = false;
|
||||
}
|
||||
String msg;
|
||||
serializeJson(json, msg);
|
||||
mqtt.publish(topic, msg.c_str());
|
||||
}
|
||||
if(mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt.connected() && !topic.isEmpty()) {
|
||||
mqttHandler->publishTemperatures(&config, &hw);
|
||||
}
|
||||
debugD("Used %d ms to update temperature", millis()-start);
|
||||
}
|
||||
@ -412,6 +371,7 @@ void loop() {
|
||||
MqttConfig mqttConfig;
|
||||
if(config.getMqttConfig(mqttConfig)) {
|
||||
mqttEnabled = strlen(mqttConfig.host) > 0;
|
||||
ws.setMqttEnabled(mqttEnabled);
|
||||
}
|
||||
}
|
||||
if(config.isNtpChanged()) {
|
||||
@ -443,9 +403,6 @@ void loop() {
|
||||
if(!mqtt.connected() || config.isMqttChanged()) {
|
||||
MQTT_connect();
|
||||
}
|
||||
if(payloadFormat == 1) {
|
||||
sendSystemStatusToMqtt();
|
||||
}
|
||||
} else if(mqtt.connected()) {
|
||||
mqtt.disconnect();
|
||||
}
|
||||
@ -470,8 +427,8 @@ void loop() {
|
||||
delay(1);
|
||||
readHanPort();
|
||||
if(WiFi.status() == WL_CONNECTED) {
|
||||
if(eapi.loop()) {
|
||||
sendPricesToMqtt();
|
||||
if(eapi.loop() && mqttHandler != NULL) {
|
||||
mqttHandler->publishPrices(&eapi);
|
||||
}
|
||||
}
|
||||
ws.loop();
|
||||
@ -620,150 +577,6 @@ void mqttMessageReceived(String &topic, String &payload)
|
||||
// Ideas could be to query for values or to initiate OTA firmware update
|
||||
}
|
||||
|
||||
void sendPricesToMqtt() {
|
||||
if(!mqttEnabled || topic.isEmpty() || !mqtt.connected())
|
||||
return;
|
||||
if(strcmp(eapi.getToken(), "") != 0)
|
||||
return;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
float min1hr, min3hr, min6hr;
|
||||
uint8_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) {
|
||||
tmElements_t tm;
|
||||
breakTime(now + (SECS_PER_HOUR * min1hrIdx), tm);
|
||||
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts3hr[21];
|
||||
if(min3hrIdx != -1) {
|
||||
tmElements_t tm;
|
||||
breakTime(now + (SECS_PER_HOUR * min3hrIdx), tm);
|
||||
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts6hr[21];
|
||||
if(min6hrIdx != -1) {
|
||||
tmElements_t tm;
|
||||
breakTime(now + (SECS_PER_HOUR * min6hrIdx), tm);
|
||||
sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
|
||||
switch(payloadFormat) {
|
||||
case 0: // JSON
|
||||
{
|
||||
StaticJsonDocument<384> json;
|
||||
json["id"] = WiFi.macAddress();
|
||||
JsonObject jp = json.createNestedObject("prices");
|
||||
for(int i = 0; i < 24; i++) {
|
||||
float val = values[i];
|
||||
if(val == ENTSOE_NO_VALUE) break;
|
||||
jp[String(i)] = serialized(String(val, 4));
|
||||
}
|
||||
if(min != INT16_MAX) {
|
||||
jp["min"] = serialized(String(min, 4));
|
||||
}
|
||||
if(max != INT16_MIN) {
|
||||
jp["max"] = serialized(String(max, 4));
|
||||
}
|
||||
|
||||
if(min1hrIdx != -1) {
|
||||
jp["cheapest1hr"] = String(ts1hr);
|
||||
}
|
||||
if(min3hrIdx != -1) {
|
||||
jp["cheapest3hr"] = String(ts3hr);
|
||||
}
|
||||
if(min6hrIdx != -1) {
|
||||
jp["cheapest6hr"] = String(ts6hr);
|
||||
}
|
||||
|
||||
String msg;
|
||||
serializeJson(json, msg);
|
||||
mqtt.publish(topic, msg.c_str());
|
||||
break;
|
||||
}
|
||||
case 1: // RAW
|
||||
case 2:
|
||||
{
|
||||
for(int i = 0; i < 24; i++) {
|
||||
float val = values[i];
|
||||
if(val == ENTSOE_NO_VALUE) {
|
||||
mqtt.publish(topic + "/price/" + String(i), "");
|
||||
break;
|
||||
} else {
|
||||
mqtt.publish(topic + "/price/" + String(i), String(val, 4));
|
||||
}
|
||||
mqtt.loop();
|
||||
delay(10);
|
||||
}
|
||||
if(min != INT16_MAX) {
|
||||
mqtt.publish(topic + "/price/min", String(min, 4));
|
||||
}
|
||||
if(max != INT16_MIN) {
|
||||
mqtt.publish(topic + "/price/max", String(max, 4));
|
||||
}
|
||||
|
||||
if(min1hrIdx != -1) {
|
||||
mqtt.publish(topic + "/price/cheapest/1hr", String(ts1hr));
|
||||
}
|
||||
if(min3hrIdx != -1) {
|
||||
mqtt.publish(topic + "/price/cheapest/3hr", String(ts3hr));
|
||||
}
|
||||
if(min6hrIdx != -1) {
|
||||
mqtt.publish(topic + "/price/cheapest/6hr", String(ts6hr));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int currentMeterType = 0;
|
||||
void readHanPort() {
|
||||
if (hanReader.read()) {
|
||||
@ -775,163 +588,13 @@ void readHanPort() {
|
||||
|
||||
AmsData data(meterConfig.type, meterConfig.substituteMissing, hanReader);
|
||||
if(data.getListType() > 0) {
|
||||
if(mqttEnabled && !topic.isEmpty()) {
|
||||
if(payloadFormat == 0) {
|
||||
StaticJsonDocument<512> json;
|
||||
hanToJson(json, data, hw, hw.getTemperature(), clientId);
|
||||
if (Debug.isActive(RemoteDebug::INFO)) {
|
||||
debugI("Sending data to MQTT");
|
||||
if (Debug.isActive(RemoteDebug::DEBUG)) {
|
||||
serializeJsonPretty(json, Debug);
|
||||
}
|
||||
if(mqttEnabled && mqttHandler != NULL) {
|
||||
if(mqttHandler->publish(&data, &meterState)) {
|
||||
if(data.getListType() == 3) {
|
||||
mqttHandler->publishPrices(&eapi);
|
||||
}
|
||||
|
||||
String msg;
|
||||
serializeJson(json, msg);
|
||||
mqtt.publish(topic, msg.c_str());
|
||||
//
|
||||
// Start DOMOTICZ
|
||||
//
|
||||
} else if(payloadFormat == 3) {
|
||||
debugI("Sending data to MQTT");
|
||||
if(config.isDomoChanged()) {
|
||||
if(domoConfig == NULL)
|
||||
domoConfig = new DomoticzConfig();
|
||||
if(config.getDomoticzConfig(*domoConfig)) {
|
||||
config.ackDomoChange();
|
||||
}
|
||||
}
|
||||
|
||||
if (domoConfig->elidx > 0) {
|
||||
if(data.getActiveImportCounter() > 1.0) {
|
||||
energy = data.getActiveImportCounter();
|
||||
}
|
||||
if(energy > 0.0) {
|
||||
String PowerEnergy;
|
||||
int p;
|
||||
StaticJsonDocument<200> json_PE;
|
||||
p = data.getActiveImportPower();
|
||||
PowerEnergy = String((double) p/1.0) + ";" + String(energy*1000.0, 0);
|
||||
json_PE["command"] = "udevice";
|
||||
json_PE["idx"] = domoConfig->elidx;
|
||||
json_PE["nvalue"] = 0;
|
||||
json_PE["svalue"] = PowerEnergy;
|
||||
String msg_PE;
|
||||
serializeJson(json_PE, msg_PE);
|
||||
mqtt.publish("domoticz/in", msg_PE.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (domoConfig->vl1idx > 0){
|
||||
if (data.getL1Voltage() > 0.1){
|
||||
StaticJsonDocument<200> json_u1;
|
||||
json_u1["command"] = "udevice";
|
||||
json_u1["idx"] = domoConfig->vl1idx;
|
||||
json_u1["nvalue"] = 0;
|
||||
json_u1["svalue"] = String(data.getL1Voltage(), 2);
|
||||
String msg_u1;
|
||||
serializeJson(json_u1, msg_u1);
|
||||
mqtt.publish("domoticz/in", msg_u1.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (domoConfig->vl2idx > 0){
|
||||
if (data.getL2Voltage() > 0.1){
|
||||
StaticJsonDocument<200> json_u2;
|
||||
json_u2["command"] = "udevice";
|
||||
json_u2["idx"] = domoConfig->vl2idx;
|
||||
json_u2["nvalue"] = 0;
|
||||
json_u2["svalue"] = String(data.getL2Voltage(), 2);
|
||||
String msg_u2;
|
||||
serializeJson(json_u2, msg_u2);
|
||||
mqtt.publish("domoticz/in", msg_u2.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (domoConfig->vl3idx > 0){
|
||||
if (data.getL3Voltage() > 0.1){
|
||||
StaticJsonDocument<200> json_u3;
|
||||
json_u3["command"] = "udevice";
|
||||
json_u3["idx"] = domoConfig->vl3idx;
|
||||
json_u3["nvalue"] = 0;
|
||||
json_u3["svalue"] = String(data.getL3Voltage());
|
||||
String msg_u3;
|
||||
serializeJson(json_u3, msg_u3);
|
||||
mqtt.publish("domoticz/in", msg_u3.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (domoConfig->cl1idx > 0){
|
||||
if(data.getL1Current() > 0.0) {
|
||||
StaticJsonDocument<200> json_i1;
|
||||
String Ampere3;
|
||||
Ampere3 = String(data.getL1Current(), 1) + ";" + String(data.getL2Current(), 1) + ";" + String(data.getL3Current(), 1) ;
|
||||
json_i1["command"] = "udevice";
|
||||
json_i1["idx"] = domoConfig->cl1idx;
|
||||
json_i1["nvalue"] = 0;
|
||||
json_i1["svalue"] = Ampere3;
|
||||
String msg_i1;
|
||||
serializeJson(json_i1, msg_i1);
|
||||
mqtt.publish("domoticz/in", msg_i1.c_str());
|
||||
}
|
||||
}
|
||||
//
|
||||
// End DOMOTICZ
|
||||
//
|
||||
} else if(payloadFormat == 1 || payloadFormat == 2) {
|
||||
if(data.getPackageTimestamp() > 0) {
|
||||
mqtt.publish(topic + "/meter/dlms/timestamp", String(data.getPackageTimestamp()));
|
||||
}
|
||||
switch(data.getListType()) {
|
||||
case 3:
|
||||
// ID and type belongs to List 2, but I see no need to send that every 10s
|
||||
mqtt.publish(topic + "/meter/id", data.getMeterId());
|
||||
mqtt.publish(topic + "/meter/type", data.getMeterType());
|
||||
mqtt.publish(topic + "/meter/clock", String(data.getMeterTimestamp()));
|
||||
mqtt.publish(topic + "/meter/import/reactive/accumulated", String(data.getReactiveImportCounter(), 2));
|
||||
mqtt.publish(topic + "/meter/import/active/accumulated", String(data.getActiveImportCounter(), 2));
|
||||
mqtt.publish(topic + "/meter/export/reactive/accumulated", String(data.getReactiveExportCounter(), 2));
|
||||
mqtt.publish(topic + "/meter/export/active/accumulated", String(data.getActiveExportCounter(), 2));
|
||||
sendPricesToMqtt();
|
||||
case 2:
|
||||
// Only send data if changed. ID and Type is sent on the 10s interval only if changed
|
||||
if(meterState.getMeterId() != data.getMeterId() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/id", data.getMeterId());
|
||||
}
|
||||
if(meterState.getMeterType() != data.getMeterType() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/type", data.getMeterType());
|
||||
}
|
||||
if(meterState.getL1Current() != data.getL1Current() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/l1/current", String(data.getL1Current(), 2));
|
||||
}
|
||||
if(meterState.getL1Voltage() != data.getL1Voltage() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/l1/voltage", String(data.getL1Voltage(), 2));
|
||||
}
|
||||
if(meterState.getL2Current() != data.getL2Current() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/l2/current", String(data.getL2Current(), 2));
|
||||
}
|
||||
if(meterState.getL2Voltage() != data.getL2Voltage() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/l2/voltage", String(data.getL2Voltage(), 2));
|
||||
}
|
||||
if(meterState.getL3Current() != data.getL3Current() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/l3/current", String(data.getL3Current(), 2));
|
||||
}
|
||||
if(meterState.getL3Voltage() != data.getL3Voltage() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/l3/voltage", String(data.getL3Voltage(), 2));
|
||||
}
|
||||
if(meterState.getReactiveExportPower() != data.getReactiveExportPower() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/export/reactive", String(data.getReactiveExportPower()));
|
||||
}
|
||||
if(meterState.getActiveExportPower() != data.getActiveExportPower() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/export/active", String(data.getActiveExportPower()));
|
||||
}
|
||||
if(meterState.getReactiveImportPower() != data.getReactiveImportPower() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/import/reactive", String(data.getReactiveImportPower()));
|
||||
}
|
||||
case 1:
|
||||
if(meterState.getActiveImportPower() != data.getActiveImportPower() || payloadFormat == 2) {
|
||||
mqtt.publish(topic + "/meter/import/active", String(data.getActiveImportPower()));
|
||||
}
|
||||
if(data.getListType() >= 2) {
|
||||
mqttHandler->publishSystem(&hw);
|
||||
}
|
||||
}
|
||||
mqtt.loop();
|
||||
@ -1044,6 +707,7 @@ void MQTT_connect() {
|
||||
if(!config.getMqttConfig(mqttConfig) || strlen(mqttConfig.host) == 0) {
|
||||
if(Debug.isActive(RemoteDebug::WARNING)) debugW("No MQTT config");
|
||||
mqttEnabled = false;
|
||||
ws.setMqttEnabled(false);
|
||||
config.ackMqttChange();
|
||||
return;
|
||||
}
|
||||
@ -1053,8 +717,8 @@ void MQTT_connect() {
|
||||
}
|
||||
lastMqttRetry = millis();
|
||||
|
||||
clientId = String(mqttConfig.clientId);
|
||||
mqttEnabled = true;
|
||||
ws.setMqttEnabled(true);
|
||||
payloadFormat = mqttConfig.payloadFormat;
|
||||
topic = String(mqttConfig.publishTopic);
|
||||
|
||||
@ -1065,6 +729,26 @@ void MQTT_connect() {
|
||||
mqtt.disconnect();
|
||||
yield();
|
||||
|
||||
if(mqttHandler != NULL) {
|
||||
delete mqttHandler;
|
||||
mqttHandler = NULL;
|
||||
}
|
||||
|
||||
switch(mqttConfig.payloadFormat) {
|
||||
case 0:
|
||||
mqttHandler = new JsonMqttHandler(&mqtt, mqttConfig.clientId, mqttConfig.publishTopic, &hw);
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
mqttHandler = new RawMqttHandler(&mqtt, mqttConfig.publishTopic, mqttConfig.payloadFormat == 2);
|
||||
break;
|
||||
case 3:
|
||||
DomoticzConfig domo;
|
||||
config.getDomoticzConfig(domo);
|
||||
mqttHandler = new DomoticzMqttHandler(&mqtt, domo);
|
||||
break;
|
||||
}
|
||||
|
||||
WiFiClientSecure *secureClient = NULL;
|
||||
Client *client = NULL;
|
||||
if(mqttConfig.ssl) {
|
||||
@ -1127,10 +811,8 @@ void MQTT_connect() {
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI(" Subscribing to [%s]\r\n", mqttConfig.subscribeTopic);
|
||||
}
|
||||
|
||||
if(payloadFormat == 0) {
|
||||
sendMqttData("Connected!");
|
||||
} else if(payloadFormat == 1 || payloadFormat == 2) {
|
||||
sendSystemStatusToMqtt();
|
||||
if(mqttHandler != NULL) {
|
||||
mqttHandler->publishSystem(&hw);
|
||||
}
|
||||
} else {
|
||||
if (Debug.isActive(RemoteDebug::ERROR)) {
|
||||
@ -1146,52 +828,3 @@ void MQTT_connect() {
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
// Send a simple string embedded in json over MQTT
|
||||
void sendMqttData(String data)
|
||||
{
|
||||
// Make sure we have configured a publish topic
|
||||
if (topic.isEmpty())
|
||||
return;
|
||||
|
||||
// Build a json with the message in a "data" attribute
|
||||
StaticJsonDocument<128> json;
|
||||
json["id"] = WiFi.macAddress();
|
||||
json["up"] = millis64()/1000;
|
||||
json["data"] = data;
|
||||
double vcc = hw.getVcc();
|
||||
if(vcc > 0) {
|
||||
json["vcc"] = vcc;
|
||||
}
|
||||
json["rssi"] = hw.getWifiRssi();
|
||||
|
||||
// Stringify the json
|
||||
String msg;
|
||||
serializeJson(json, msg);
|
||||
|
||||
// Send the json over MQTT
|
||||
mqtt.publish(topic, msg.c_str());
|
||||
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI("Sending MQTT data");
|
||||
if (Debug.isActive(RemoteDebug::DEBUG)) debugD("[%s]", data.c_str());
|
||||
}
|
||||
|
||||
unsigned long lastSystemDataSent = -10000;
|
||||
void sendSystemStatusToMqtt() {
|
||||
if (topic.isEmpty())
|
||||
return;
|
||||
if(millis() - lastSystemDataSent < 10000)
|
||||
return;
|
||||
lastSystemDataSent = millis();
|
||||
|
||||
mqtt.publish(topic + "/id", WiFi.macAddress());
|
||||
mqtt.publish(topic + "/uptime", String((unsigned long) millis64()/1000));
|
||||
double vcc = hw.getVcc();
|
||||
if(vcc > 0) {
|
||||
mqtt.publish(topic + "/vcc", String(vcc, 2));
|
||||
}
|
||||
mqtt.publish(topic + "/rssi", String(hw.getWifiRssi()));
|
||||
if(hw.getTemperature() > -85) {
|
||||
mqtt.publish(topic + "/temperature", String(hw.getTemperature(), 2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
#include "HanToJson.h"
|
||||
|
||||
void hanToJson(JsonDocument& json, AmsData& data, HwTools& hw, double temperature, String name) {
|
||||
json["id"] = WiFi.macAddress();
|
||||
json["name"] = name;
|
||||
json["up"] = millis();
|
||||
json["t"] = data.getPackageTimestamp();
|
||||
|
||||
double vcc = hw.getVcc();
|
||||
if(vcc > 0) {
|
||||
json["vcc"] = serialized(String(vcc, 3));
|
||||
}
|
||||
|
||||
json["rssi"] = hw.getWifiRssi();
|
||||
|
||||
if(temperature != DEVICE_DISCONNECTED_C) {
|
||||
json["temp"] = serialized(String(temperature, 2));
|
||||
}
|
||||
|
||||
// Add a sub-structure to the json object,
|
||||
// to keep the data from the meter itself
|
||||
JsonObject jd = json.createNestedObject("data");
|
||||
|
||||
switch(data.getListType()) {
|
||||
case 3:
|
||||
jd["rtc"] = data.getMeterTimestamp();
|
||||
jd["tPI"] = data.getActiveImportCounter();
|
||||
jd["tPO"] = data.getActiveExportCounter();
|
||||
jd["tQI"] = data.getReactiveImportCounter();
|
||||
jd["tQO"] = data.getReactiveExportCounter();
|
||||
case 2:
|
||||
jd["lv"] = data.getListId();
|
||||
jd["id"] = data.getMeterId();
|
||||
jd["type"] = data.getMeterType();
|
||||
jd["Q"] = data.getReactiveImportPower();
|
||||
jd["PO"] = data.getActiveExportPower();
|
||||
jd["QO"] = data.getReactiveExportPower();
|
||||
jd["I1"] = data.getL1Current();
|
||||
jd["I2"] = data.getL2Current();
|
||||
jd["I3"] = data.getL3Current();
|
||||
jd["U1"] = data.getL1Voltage();
|
||||
jd["U2"] = data.getL2Voltage();
|
||||
jd["U3"] = data.getL3Voltage();
|
||||
case 1:
|
||||
jd["P"] = data.getActiveImportPower();
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
#ifndef _HANTOJSON_h
|
||||
#define _HANTOJSON_h
|
||||
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
#include "Arduino.h"
|
||||
#else
|
||||
#include "WProgram.h"
|
||||
#endif
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include "AmsData.h"
|
||||
#include "HwTools.h"
|
||||
|
||||
void hanToJson(JsonDocument& json, AmsData& data, HwTools& hw, double temperature, String name);
|
||||
|
||||
#endif
|
||||
23
src/hexutils.cpp
Normal file
23
src/hexutils.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include "hexutils.h";
|
||||
|
||||
String toHex(uint8_t* in) {
|
||||
return toHex(in, sizeof(in)*2);
|
||||
}
|
||||
|
||||
String toHex(uint8_t* in, uint8_t size) {
|
||||
String hex;
|
||||
for(int i = 0; i < size; i++) {
|
||||
if(in[i] < 0x10) {
|
||||
hex += '0';
|
||||
}
|
||||
hex += String(in[i], HEX);
|
||||
}
|
||||
hex.toUpperCase();
|
||||
return hex;
|
||||
}
|
||||
|
||||
void fromHex(uint8_t *out, String in, uint8_t size) {
|
||||
for(int i = 0; i < size*2; i += 2) {
|
||||
out[i/2] = strtol(in.substring(i, i+2).c_str(), 0, 16);
|
||||
}
|
||||
}
|
||||
11
src/hexutils.h
Normal file
11
src/hexutils.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef _HEXUTILS_H
|
||||
#define _HEXUTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "Arduino.h"
|
||||
|
||||
String toHex(uint8_t* in);
|
||||
String toHex(uint8_t* in, uint8_t size);
|
||||
void fromHex(uint8_t *out, String in, uint8_t size);
|
||||
|
||||
#endif
|
||||
26
src/mqtt/AmsMqttHandler.h
Normal file
26
src/mqtt/AmsMqttHandler.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef _AMSMQTTHANDLER_H
|
||||
#define _AMSMQTTHANDLER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <MQTT.h>
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "HwTools.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
|
||||
class AmsMqttHandler {
|
||||
public:
|
||||
AmsMqttHandler(MQTTClient* mqtt) {
|
||||
this->mqtt = mqtt;
|
||||
};
|
||||
|
||||
virtual bool publish(AmsData* data, AmsData* previousState);
|
||||
virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
virtual bool publishPrices(EntsoeApi* eapi);
|
||||
virtual bool publishSystem(HwTools*);
|
||||
|
||||
protected:
|
||||
MQTTClient* mqtt;
|
||||
};
|
||||
|
||||
#endif
|
||||
89
src/mqtt/DomoticzMqttHandler.cpp
Normal file
89
src/mqtt/DomoticzMqttHandler.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
#include "DomoticzMqttHandler.h"
|
||||
#include "web/root/domoticz_json.h"
|
||||
|
||||
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState) {
|
||||
bool ret = false;
|
||||
if (config.elidx > 0) {
|
||||
if(data->getActiveImportCounter() > 1.0) {
|
||||
energy = data->getActiveImportCounter();
|
||||
}
|
||||
if(energy > 0.0) {
|
||||
char val[16];
|
||||
snprintf(val, 16, "%.1f;%.1f", (data->getActiveImportPower()/1.0), energy*1000.0);
|
||||
char json[192];
|
||||
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
|
||||
config.elidx,
|
||||
val
|
||||
);
|
||||
ret = mqtt->publish("domoticz/in", json);
|
||||
}
|
||||
}
|
||||
|
||||
if(data->getListType() == 1)
|
||||
return ret;
|
||||
|
||||
if (config.vl1idx > 0){
|
||||
if (data->getL1Voltage() > 0.1){
|
||||
char val[16];
|
||||
snprintf(val, 16, "%.2f", data->getL1Voltage());
|
||||
char json[192];
|
||||
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
|
||||
config.vl1idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt->publish("domoticz/in", json);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.vl2idx > 0){
|
||||
if (data->getL2Voltage() > 0.1){
|
||||
char val[16];
|
||||
snprintf(val, 16, "%.2f", data->getL2Voltage());
|
||||
char json[192];
|
||||
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
|
||||
config.vl2idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt->publish("domoticz/in", json);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.vl3idx > 0){
|
||||
if (data->getL3Voltage() > 0.1){
|
||||
char val[16];
|
||||
snprintf(val, 16, "%.2f", data->getL3Voltage());
|
||||
char json[192];
|
||||
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
|
||||
config.vl3idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt->publish("domoticz/in", json);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.cl1idx > 0){
|
||||
if(data->getL1Current() > 0.0) {
|
||||
char val[16];
|
||||
snprintf(val, 16, "%.1f;%.1f;%.1f", data->getL1Current(), data->getL2Current(), data->getL3Current());
|
||||
char json[192];
|
||||
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
|
||||
config.cl1idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt->publish("domoticz/in", json);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool DomoticzMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomoticzMqttHandler::publishSystem(HwTools* hw) {
|
||||
return false;
|
||||
}
|
||||
21
src/mqtt/DomoticzMqttHandler.h
Normal file
21
src/mqtt/DomoticzMqttHandler.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef _DOMOTICZMQTTHANDLER_H
|
||||
#define _DOMOTICZMQTTHANDLER_H
|
||||
|
||||
#include "AmsMqttHandler.h"
|
||||
#include "AmsConfiguration.h"
|
||||
|
||||
class DomoticzMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
DomoticzMqttHandler(MQTTClient* mqtt, DomoticzConfig config) : AmsMqttHandler(mqtt) {
|
||||
this->config = config;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
|
||||
private:
|
||||
DomoticzConfig config;
|
||||
int energy = 0.0;
|
||||
};
|
||||
#endif
|
||||
227
src/mqtt/JsonMqttHandler.cpp
Normal file
227
src/mqtt/JsonMqttHandler.cpp
Normal file
@ -0,0 +1,227 @@
|
||||
#include "JsonMqttHandler.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/jsonsys_json.h"
|
||||
#include "web/root/jsonprices_json.h"
|
||||
|
||||
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
double vcc = hw->getVcc();
|
||||
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(),
|
||||
vcc,
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
data->getActiveImportPower()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
} else if(data->getListType() == 2) {
|
||||
char json[256];
|
||||
snprintf_P(json, sizeof(json), JSON2_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
clientId.c_str(),
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
vcc,
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
data->getMeterType().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) {
|
||||
char json[384];
|
||||
snprintf_P(json, sizeof(json), JSON3_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
clientId.c_str(),
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
vcc,
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
data->getMeterType().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);
|
||||
}
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::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);
|
||||
TempSensorConfig* conf = config->getTempSensorConfig(data->address);
|
||||
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 JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
if(strcmp(eapi->getToken(), "") != 0)
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
float min1hr, min3hr, min6hr;
|
||||
uint8_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) {
|
||||
tmElements_t tm;
|
||||
breakTime(now + (SECS_PER_HOUR * min1hrIdx), tm);
|
||||
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts3hr[21];
|
||||
if(min3hrIdx != -1) {
|
||||
tmElements_t tm;
|
||||
breakTime(now + (SECS_PER_HOUR * min3hrIdx), tm);
|
||||
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts6hr[21];
|
||||
if(min6hrIdx != -1) {
|
||||
tmElements_t tm;
|
||||
breakTime(now + (SECS_PER_HOUR * min6hrIdx), 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 JsonMqttHandler::publishSystem(HwTools* hw) {
|
||||
if(init || topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
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;
|
||||
}
|
||||
24
src/mqtt/JsonMqttHandler.h
Normal file
24
src/mqtt/JsonMqttHandler.h
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef _JSONMQTTHANDLER_H
|
||||
#define _JSONMQTTHANDLER_H
|
||||
|
||||
#include "AmsMqttHandler.h"
|
||||
|
||||
class JsonMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
JsonMqttHandler(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
|
||||
200
src/mqtt/RawMqttHandler.cpp
Normal file
200
src/mqtt/RawMqttHandler.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
#include "RawMqttHandler.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
bool RawMqttHandler::publish(AmsData* data, AmsData* meterState) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
if(data->getPackageTimestamp() > 0) {
|
||||
mqtt->publish(topic + "/meter/dlms/timestamp", String(data->getPackageTimestamp()));
|
||||
}
|
||||
switch(data->getListType()) {
|
||||
case 3:
|
||||
// ID and type belongs to List 2, but I see no need to send that every 10s
|
||||
mqtt->publish(topic + "/meter/id", data->getMeterId());
|
||||
mqtt->publish(topic + "/meter/type", data->getMeterType());
|
||||
mqtt->publish(topic + "/meter/clock", String(data->getMeterTimestamp()));
|
||||
mqtt->publish(topic + "/meter/import/reactive/accumulated", String(data->getReactiveImportCounter(), 2));
|
||||
mqtt->publish(topic + "/meter/import/active/accumulated", String(data->getActiveImportCounter(), 2));
|
||||
mqtt->publish(topic + "/meter/export/reactive/accumulated", String(data->getReactiveExportCounter(), 2));
|
||||
mqtt->publish(topic + "/meter/export/active/accumulated", String(data->getActiveExportCounter(), 2));
|
||||
case 2:
|
||||
// Only send data if changed. ID and Type is sent on the 10s interval only if changed
|
||||
if(full || meterState->getMeterId() != data->getMeterId()) {
|
||||
mqtt->publish(topic + "/meter/id", data->getMeterId());
|
||||
}
|
||||
if(full || meterState->getMeterType() != data->getMeterType()) {
|
||||
mqtt->publish(topic + "/meter/type", data->getMeterType());
|
||||
}
|
||||
if(full || meterState->getL1Current() != data->getL1Current()) {
|
||||
mqtt->publish(topic + "/meter/l1/current", String(data->getL1Current(), 2));
|
||||
}
|
||||
if(full || meterState->getL1Voltage() != data->getL1Voltage()) {
|
||||
mqtt->publish(topic + "/meter/l1/voltage", String(data->getL1Voltage(), 2));
|
||||
}
|
||||
if(full || meterState->getL2Current() != data->getL2Current()) {
|
||||
mqtt->publish(topic + "/meter/l2/current", String(data->getL2Current(), 2));
|
||||
}
|
||||
if(full || meterState->getL2Voltage() != data->getL2Voltage()) {
|
||||
mqtt->publish(topic + "/meter/l2/voltage", String(data->getL2Voltage(), 2));
|
||||
}
|
||||
if(full || meterState->getL3Current() != data->getL3Current()) {
|
||||
mqtt->publish(topic + "/meter/l3/current", String(data->getL3Current(), 2));
|
||||
}
|
||||
if(full || meterState->getL3Voltage() != data->getL3Voltage()) {
|
||||
mqtt->publish(topic + "/meter/l3/voltage", String(data->getL3Voltage(), 2));
|
||||
}
|
||||
if(full || meterState->getReactiveExportPower() != data->getReactiveExportPower()) {
|
||||
mqtt->publish(topic + "/meter/export/reactive", String(data->getReactiveExportPower()));
|
||||
}
|
||||
if(full || meterState->getActiveExportPower() != data->getActiveExportPower()) {
|
||||
mqtt->publish(topic + "/meter/export/active", String(data->getActiveExportPower()));
|
||||
}
|
||||
if(full || meterState->getReactiveImportPower() != data->getReactiveImportPower()) {
|
||||
mqtt->publish(topic + "/meter/import/reactive", String(data->getReactiveImportPower()));
|
||||
}
|
||||
case 1:
|
||||
if(full || meterState->getActiveImportPower() != data->getActiveImportPower()) {
|
||||
mqtt->publish(topic + "/meter/import/active", String(data->getActiveImportPower()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
|
||||
uint8_t c = hw->getTempSensorCount();
|
||||
for(int i = 0; i < c; i++) {
|
||||
TempSensorData* data = hw->getTempSensorData(i);
|
||||
if(data->lastValidRead > -85) {
|
||||
if(data->changed || full) {
|
||||
mqtt->publish(topic + "/temperature/" + toHex(data->address), String(data->lastValidRead, 2));
|
||||
data->changed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return c > 0;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
if(strcmp(eapi->getToken(), "") != 0)
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
float min1hr, min3hr, min6hr;
|
||||
uint8_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) {
|
||||
tmElements_t tm;
|
||||
breakTime(now + (SECS_PER_HOUR * min1hrIdx), tm);
|
||||
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts3hr[21];
|
||||
if(min3hrIdx != -1) {
|
||||
tmElements_t tm;
|
||||
breakTime(now + (SECS_PER_HOUR * min3hrIdx), tm);
|
||||
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts6hr[21];
|
||||
if(min6hrIdx != -1) {
|
||||
tmElements_t tm;
|
||||
breakTime(now + (SECS_PER_HOUR * min6hrIdx), tm);
|
||||
sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
|
||||
for(int i = 0; i < 24; i++) {
|
||||
float val = values[i];
|
||||
if(val == ENTSOE_NO_VALUE) {
|
||||
mqtt->publish(topic + "/price/" + String(i), "");
|
||||
break;
|
||||
} else {
|
||||
mqtt->publish(topic + "/price/" + String(i), String(val, 4));
|
||||
}
|
||||
mqtt->loop();
|
||||
delay(10);
|
||||
}
|
||||
if(min != INT16_MAX) {
|
||||
mqtt->publish(topic + "/price/min", String(min, 4));
|
||||
}
|
||||
if(max != INT16_MIN) {
|
||||
mqtt->publish(topic + "/price/max", String(max, 4));
|
||||
}
|
||||
|
||||
if(min1hrIdx != -1) {
|
||||
mqtt->publish(topic + "/price/cheapest/1hr", String(ts1hr));
|
||||
}
|
||||
if(min3hrIdx != -1) {
|
||||
mqtt->publish(topic + "/price/cheapest/3hr", String(ts3hr));
|
||||
}
|
||||
if(min6hrIdx != -1) {
|
||||
mqtt->publish(topic + "/price/cheapest/6hr", String(ts6hr));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishSystem(HwTools* hw) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
mqtt->publish(topic + "/id", WiFi.macAddress());
|
||||
mqtt->publish(topic + "/uptime", String((unsigned long) millis64()/1000));
|
||||
float vcc = hw->getVcc();
|
||||
if(vcc > 0) {
|
||||
mqtt->publish(topic + "/vcc", String(vcc, 2));
|
||||
}
|
||||
mqtt->publish(topic + "/rssi", String(hw->getWifiRssi()));
|
||||
if(hw->getTemperature() > -85) {
|
||||
mqtt->publish(topic + "/temperature", String(hw->getTemperature(), 2));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
21
src/mqtt/RawMqttHandler.h
Normal file
21
src/mqtt/RawMqttHandler.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef _RAWMQTTHANDLER_H
|
||||
#define _RAWMQTTHANDLER_H
|
||||
|
||||
#include "AmsMqttHandler.h"
|
||||
|
||||
class RawMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
RawMqttHandler(MQTTClient* mqtt, const char* topic, bool full) : AmsMqttHandler(mqtt) {
|
||||
this->topic = String(topic);
|
||||
this->full = full;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
|
||||
private:
|
||||
String topic;
|
||||
bool full;
|
||||
};
|
||||
#endif
|
||||
@ -1,6 +1,7 @@
|
||||
#include "AmsWebServer.h"
|
||||
#include "version.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
#include "root/head_html.h"
|
||||
#include "root/foot_html.h"
|
||||
@ -101,6 +102,10 @@ void AmsWebServer::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
}
|
||||
|
||||
void AmsWebServer::setMqttEnabled(bool enabled) {
|
||||
mqttEnabled = enabled;
|
||||
}
|
||||
|
||||
void AmsWebServer::loop() {
|
||||
server.handleClient();
|
||||
|
||||
@ -192,7 +197,7 @@ void AmsWebServer::temperatureJson() {
|
||||
int size = 32 + (count * 72);
|
||||
|
||||
char buf[size];
|
||||
snprintf_P(buf, 16, "{\"c\":%d,\"s\":[", count);
|
||||
snprintf(buf, 16, "{\"c\":%d,\"s\":[", count);
|
||||
|
||||
for(int i = 0; i < count; i++) {
|
||||
TempSensorData* data = hw->getTempSensorData(i);
|
||||
@ -208,7 +213,7 @@ void AmsWebServer::temperatureJson() {
|
||||
delay(1);
|
||||
}
|
||||
char* pos = buf+strlen(buf);
|
||||
snprintf_P(count == 0 ? pos : pos-1, 8, "]}");
|
||||
snprintf(count == 0 ? pos : pos-1, 8, "]}");
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
@ -391,24 +396,6 @@ void AmsWebServer::configMeterHtml() {
|
||||
server.sendContent_P(FOOT_HTML);
|
||||
}
|
||||
|
||||
String AmsWebServer::toHex(uint8_t* in, uint8_t size) {
|
||||
String hex;
|
||||
for(int i = 0; i < size; i++) {
|
||||
if(in[i] < 0x10) {
|
||||
hex += '0';
|
||||
}
|
||||
hex += String(in[i], HEX);
|
||||
}
|
||||
hex.toUpperCase();
|
||||
return hex;
|
||||
}
|
||||
|
||||
void AmsWebServer::fromHex(uint8_t *out, String in, uint8_t size) {
|
||||
for(int i = 0; i < size*2; i += 2) {
|
||||
out[i/2] = strtol(in.substring(i, i+2).c_str(), 0, 16);
|
||||
}
|
||||
}
|
||||
|
||||
void AmsWebServer::configWifiHtml() {
|
||||
printD("Serving /wifi.html over http...");
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ public:
|
||||
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, MQTTClient*);
|
||||
void loop();
|
||||
void setTimezone(Timezone* tz);
|
||||
void setMqttEnabled(bool);
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger;
|
||||
@ -109,9 +110,6 @@ private:
|
||||
|
||||
void notFound();
|
||||
|
||||
String toHex(uint8_t* in, uint8_t size);
|
||||
void fromHex(uint8_t *out, String in, uint8_t size);
|
||||
|
||||
void printD(String fmt, ...);
|
||||
void printI(String fmt, ...);
|
||||
void printW(String fmt, ...);
|
||||
|
||||
@ -272,9 +272,9 @@ var fetch = function() {
|
||||
}
|
||||
|
||||
setStatus("esp", json.em);
|
||||
setStatus("han", json.em == 3 ? 0 : json.hm);
|
||||
setStatus("wifi", json.em == 3 ? 0 : json.wm);
|
||||
setStatus("mqtt", json.em == 3 ? 0 : json.mm);
|
||||
setStatus("han", json.hm);
|
||||
setStatus("wifi", json.wm);
|
||||
setStatus("mqtt", json.mm);
|
||||
|
||||
|
||||
if(im && im.gaugeMeter) {
|
||||
@ -348,10 +348,10 @@ var fetch = function() {
|
||||
}).fail(function() {
|
||||
setTimeout(fetch, interval*4);
|
||||
|
||||
setStatus("mqtt", "secondary");
|
||||
setStatus("wifi", "secondary");
|
||||
setStatus("han", "secondary");
|
||||
setStatus("esp", "danger");
|
||||
setStatus("mqtt", 0);
|
||||
setStatus("wifi", 0);
|
||||
setStatus("han", 0);
|
||||
setStatus("esp", 3);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
6
web/domoticz.json
Normal file
6
web/domoticz.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"command" : "udevice",
|
||||
"idx" : %d,
|
||||
"nvalue" : 0,
|
||||
"svalue" : "%s"
|
||||
}
|
||||
12
web/json1.json
Normal file
12
web/json1.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %d,
|
||||
"t" : %d,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"P" : %d
|
||||
}
|
||||
}
|
||||
24
web/json2.json
Normal file
24
web/json2.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %d,
|
||||
"t" : %d,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f
|
||||
}
|
||||
}
|
||||
29
web/json3.json
Normal file
29
web/json3.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %d,
|
||||
"t" : %d,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f,
|
||||
"tPI" : %.1f,
|
||||
"tPO" : %.1f,
|
||||
"tQI" : %.1f,
|
||||
"tQO" : %.1f,
|
||||
"rtc" : %d
|
||||
}
|
||||
}
|
||||
23
web/jsonprices.json
Normal file
23
web/jsonprices.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"prices" : {
|
||||
"0" : %.4f,
|
||||
"1" : %.4f,
|
||||
"2" : %.4f,
|
||||
"3" : %.4f,
|
||||
"4" : %.4f,
|
||||
"5" : %.4f,
|
||||
"6" : %.4f,
|
||||
"7" : %.4f,
|
||||
"8" : %.4f,
|
||||
"9" : %.4f,
|
||||
"10" : %.4f,
|
||||
"11" : %.4f,
|
||||
"12" : %.4f,
|
||||
"min" : %.4f,
|
||||
"max" : %.4f,
|
||||
"cheapest1hr" : "%s",
|
||||
"cheapest3hr" : "%s",
|
||||
"cheapest6hr" : "%s"
|
||||
}
|
||||
}
|
||||
8
web/jsonsys.json
Normal file
8
web/jsonsys.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %d,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user