From dbe370809249e64ebdfdeaa0d5c39d277d442c4e Mon Sep 17 00:00:00 2001 From: stromibaer <47776842+stromibaer@users.noreply.github.com> Date: Thu, 20 Oct 2022 20:37:42 +0200 Subject: [PATCH 1/5] Add support for Vitron Cerbo / VRM portal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit added some source to publish Hoymiles values via MqTT to Victron Venus OS mosquitto server, subscribe at Victron OS mosquitto server to get portal ID and deviceInstance. Add support for Victron Cerbo / VRM portal Some new functions and variables for publishing values via MqTT to Victron Venus OS mosquitto server. code cleaning victron part in webapp Current phase input publisch phase bugfix current phase bugfix hh lll df ll dsgdsg xvccxv sfsfd sdfsdf dgfsdf hh ff dgdfg d dsfdsf asf Update code from master Runden und Schleifenänderung round#1 round#2 round#3 round#4 Searching for value 0 in Voltage Finding some errors Fieldname and value corrected sfdsdf adfdsf Try to save some memory Loopänderung um Registrierung nicht alle 5 sekunden auszuführen. Changed inverter registration procedure, similar to Hass Fehler in Variablenvereinbarung behoben. Maxpower & Errorcode in loop eingefügt --- include/Configuration.h | 3 + include/MqttSettings.h | 9 + include/MqttVictronPublishing.h | 43 +++++ include/defaults.h | 4 +- src/Configuration.cpp | 8 + src/MqttPublishing.cpp | 78 +++++++++ src/MqttSettings.cpp | 88 +++++++++- src/MqttVictronPublishing.cpp | 224 +++++++++++++++++++++++++ src/WebApi_inverter.cpp | 8 + src/WebApi_mqtt.cpp | 6 +- src/main.cpp | 3 + webapp/src/types/MqttConfig.ts | 1 + webapp/src/types/MqttStatus.ts | 1 + webapp/src/views/InverterAdminView.vue | 22 +++ webapp/src/views/MqttAdminView.vue | 11 ++ webapp_dist/index.html.gz | Bin 329 -> 336 bytes webapp_dist/zones.json.gz | Bin 4100 -> 4121 bytes 17 files changed, 505 insertions(+), 4 deletions(-) create mode 100644 include/MqttVictronPublishing.h create mode 100644 src/MqttVictronPublishing.cpp diff --git a/include/Configuration.h b/include/Configuration.h index a6a986f54..30c797179 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -37,6 +37,7 @@ struct CHANNEL_CONFIG_T { struct INVERTER_CONFIG_T { uint64_t Serial; char Name[INV_MAX_NAME_STRLEN + 1]; + uint16_t CurrentPhase; CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT]; }; @@ -87,6 +88,8 @@ struct CONFIG_T { bool Mqtt_Hass_Expire; char Security_Password[WIFI_MAX_PASSWORD_STRLEN + 1]; + + bool Mqtt_Victron_Enabled; }; class ConfigurationClass { diff --git a/include/MqttSettings.h b/include/MqttSettings.h index afa3283c8..61de27db3 100644 --- a/include/MqttSettings.h +++ b/include/MqttSettings.h @@ -6,6 +6,7 @@ #include #include #include +#include class MqttSettingsClass { public: @@ -16,6 +17,14 @@ class MqttSettingsClass { void publish(const String& subtopic, const String& payload); void publishHass(const String& subtopic, const String& payload); + void publishVictron(const String& hoyserial, const String& payload); + + const char* VictronPortalId; + std::map VictronDeviceInstance; + + String getVictronPortalId(); + String getVictronDeviceInstance(String hoyserial); + String getPrefix(); private: diff --git a/include/MqttVictronPublishing.h b/include/MqttVictronPublishing.h new file mode 100644 index 000000000..dbf586830 --- /dev/null +++ b/include/MqttVictronPublishing.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "Configuration.h" +#include +#include +#include + +class MqttVictronPublishingClass { +public: + void init(); + void loop(); + void forceRegister(); + void registerInverter(); + +private: + void publishField(std::shared_ptr inv, uint8_t invphase, uint8_t fieldId); + + uint32_t _lastPublishStats[INV_MAX_COUNT]; + uint32_t _lastPublish; + + uint8_t _publishFields[14] = { + FLD_UDC, + FLD_IDC, + FLD_PDC, + FLD_YD, + FLD_YT, + FLD_UAC, + FLD_IAC, + FLD_PAC, + FLD_F, + FLD_T, + FLD_PF, + FLD_EFF, + FLD_IRR, + FLD_PRA + }; + + bool _wasConnected = false; + bool _registerForced = false; +}; + +extern MqttVictronPublishingClass MqttVictronPublishing; \ No newline at end of file diff --git a/include/defaults.h b/include/defaults.h index 9656901d8..9367302a0 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -76,4 +76,6 @@ #define MQTT_HASS_EXPIRE true #define MQTT_HASS_RETAIN true #define MQTT_HASS_TOPIC "homeassistant/" -#define MQTT_HASS_INDIVIDUALPANELS false \ No newline at end of file +#define MQTT_HASS_INDIVIDUALPANELS false + +#define MQTT_VICTRON_ENABLED false \ No newline at end of file diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 0842f4cca..ab755fea7 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -78,11 +78,15 @@ bool ConfigurationClass::write() JsonObject security = doc.createNestedObject("security"); security["password"] = config.Security_Password; + JsonObject mqtt_victron = mqtt.createNestedObject("victron"); + mqtt_victron["enabled"] = config.Mqtt_Victron_Enabled; + JsonArray inverters = doc.createNestedArray("inverters"); for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { JsonObject inv = inverters.createNestedObject(); inv["serial"] = config.Inverter[i].Serial; inv["name"] = config.Inverter[i].Name; + inv["phase"] = config.Inverter[i].CurrentPhase; JsonArray channel = inv.createNestedArray("channel"); for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { @@ -198,11 +202,15 @@ bool ConfigurationClass::read() JsonObject security = doc["security"]; strlcpy(config.Security_Password, security["password"] | ACCESS_POINT_PASSWORD, sizeof(config.Security_Password)); + JsonObject mqtt_victron = mqtt["victron"]; + config.Mqtt_Victron_Enabled = mqtt_victron["enabled"] | MQTT_VICTRON_ENABLED; + JsonArray inverters = doc["inverters"]; for (uint8_t i = 0; i < INV_MAX_COUNT; i++) { JsonObject inv = inverters[i].as(); config.Inverter[i].Serial = inv["serial"] | 0ULL; strlcpy(config.Inverter[i].Name, inv["name"] | "", sizeof(config.Inverter[i].Name)); + config.Inverter[i].CurrentPhase = inv["phase"] | 0ULL; JsonArray channel = inv["channel"]; for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { diff --git a/src/MqttPublishing.cpp b/src/MqttPublishing.cpp index 9bc73b057..51b7b1c48 100644 --- a/src/MqttPublishing.cpp +++ b/src/MqttPublishing.cpp @@ -2,10 +2,12 @@ /* * Copyright (C) 2022 Thomas Basler and others */ +#include "ArduinoJson.h" #include "MqttPublishing.h" #include "MqttSettings.h" #include "NetworkSettings.h" #include +#include MqttPublishingClass MqttPublishing; @@ -65,6 +67,27 @@ void MqttPublishingClass::loop() uint16_t maxpower = inv->DevInfo()->getMaxPower(); if (maxpower > 0) { MqttSettings.publish(subtopic + "/status/limit_absolute", String(inv->SystemConfigPara()->getLimitPercent() * maxpower / 100)); + + // Publish maxpower and error code to Victron Venus OS + String deviceInstance = MqttSettings.getVictronDeviceInstance(subtopic); + String portalid = MqttSettings.getVictronPortalId(); + if ( portalid == NULL ) { portalid = "NOportalId"; } + + String Vtopic = "W/" + portalid + "/pvinverter/" + deviceInstance + "/ErrorCode"; + DynamicJsonDocument val1Doc(128); + val1Doc["value"] = 0; + JsonObject val1Obj = val1Doc.as(); + String data1; + serializeJson(val1Obj, data1); + MqttSettings.publishVictron(Vtopic, data1); + + Vtopic = "W/" + portalid + "/pvinverter/" + deviceInstance + "/Ac/MaxPower"; + DynamicJsonDocument val2Doc(128); + val2Doc["value"] = maxpower; + JsonObject val2Obj = val2Doc.as(); + String data2; + serializeJson(val2Obj, data2); + MqttSettings.publishVictron(Vtopic, data2); } } @@ -110,6 +133,61 @@ void MqttPublishingClass::publishField(std::shared_ptr inv, ui } MqttSettings.publish(topic, String(inv->Statistics()->getChannelFieldValue(channel, fieldId))); + + // topic = "W/{}/pvinverter/{}/{}".format(portalId, deviceId, key) # UPDATE THIS + // print("{} = {}".format(topic, data.get(key) ) ) + // client.publish(topic, json.dumps({ "value": data.get(key) }) ) + if (channel == 0) { + + float fieldvalue = float(inv->Statistics()->getChannelFieldValue(channel, fieldId)); + fieldvalue = round(fieldvalue * 100)/100; + String fieldname = (inv->Statistics()->getChannelFieldName(channel, fieldId)); + String portalid = MqttSettings.getVictronPortalId(); + if ( portalid == NULL ) { portalid = "NOportalId"; } + String topic = "W/" + portalid + "/pvinverter"; + String topic_Victron_sum; + String topic_Victron_phase; + + char serial[sizeof(uint64_t) * 8 + 1]; + snprintf(serial, sizeof(serial), "%0x%08x", + ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), + ((uint32_t)(inv->serial() & 0xFFFFFFFF))); + String invSerial = String(serial); + + String deviceInstance = MqttSettings.getVictronDeviceInstance(serial); + + int response = false; + if ( fieldname == "Voltage" ) { response = true; } + if ( fieldname == "Power") { response = true; } + if ( fieldname == "Current" ) { response = true; } + if ( fieldname == "YieldTotal") { response = true; } + + if ( response ) { + // fieldname[0] = std::toupper(fieldname[0]); + if ( fieldname == "YieldTotal" ) { + topic_Victron_sum += topic + "/" + deviceInstance + "/Ac/Energy/Forward"; + topic_Victron_phase += topic + "/" + deviceInstance + "/Ac/L1/Energy/Forward"; + } else { + topic_Victron_sum += topic + "/" + deviceInstance + "/Ac/" + fieldname; + topic_Victron_phase += topic + "/" + deviceInstance + "/Ac/L1/" + fieldname; + } + + DynamicJsonDocument valueDoc(256); + valueDoc["value"] = fieldvalue; + JsonObject valueObj = valueDoc.as(); + + String data; + serializeJson(valueObj, data); + + Serial.print(F("MqTT publish value: ")); + Serial.print(data); + Serial.print(F(" to Venus OS with topic: ")); + Serial.println(topic_Victron_sum); + + MqttSettings.publishVictron(topic_Victron_sum, data); + MqttSettings.publishVictron(topic_Victron_phase, data); + } + } } String MqttPublishingClass::getTopic(std::shared_ptr inv, uint8_t channel, uint8_t fieldId) diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index 516409bdb..f19a1f2eb 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2022 Thomas Basler and others */ #include "MqttSettings.h" +#include "ArduinoJson.h" #include "Configuration.h" #include "NetworkSettings.h" #include @@ -50,6 +51,22 @@ void MqttSettingsClass::onMqttConnect(bool sessionPresent) mqttClient->subscribe(String(topic + "+/cmd/" + TOPIC_SUB_LIMIT_NONPERSISTENT_ABSOLUTE).c_str(), 0); mqttClient->subscribe(String(topic + "+/cmd/" + TOPIC_SUB_POWER).c_str(), 0); mqttClient->subscribe(String(topic + "+/cmd/" + TOPIC_SUB_RESTART).c_str(), 0); + + // Check for doing some Victron parts... + if (!Configuration.get().Mqtt_Victron_Enabled) { + return; + } + // Loop all inverters and subscribe each for Victron Venus messages + for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { + auto inv = Hoymiles.getInverterByPos(i); + + char buffer[sizeof(uint64_t) * 8 + 1]; + snprintf(buffer, sizeof(buffer), "%0x%08x", + ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), + ((uint32_t)(inv->serial() & 0xFFFFFFFF))); + String str_serial = String(buffer); + mqttClient->subscribe(String("device/HM" + str_serial + "/DBus").c_str(),0); + } } void MqttSettingsClass::onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) @@ -102,17 +119,62 @@ void MqttSettingsClass::onMqttMessage(const espMqttClientTypes::MessagePropertie subtopic = strtok_r(rest, "/", &rest); setting = strtok_r(rest, "/", &rest); + uint64_t serial; + if (serial_str == NULL || subtopic == NULL || setting == NULL) { + + // Victron message only on startup after subscribe + // device/116181045449/DBus = JSON + char* rest = &token_topic[strlen("device/HM")]; + serial_str = strtok_r(rest, "/", &rest); + + if (serial_str == NULL) { + return; + } + + serial = strtoull(serial_str, 0, 16); + auto inv = Hoymiles.getInverterBySerial(serial); + + if (inv == nullptr) { + Serial.print(F("Can not register inverter: ")); + Serial.println(serial); + return; + } + + char* strlimit = new char[len + 1]; + memcpy(strlimit, payload, len); + strlimit[len] = '\0'; + + DynamicJsonDocument docDbus(64); + deserializeJson(docDbus, strlimit); + VictronPortalId = docDbus["portalId"]; + + DynamicJsonDocument docInstance(64); + docInstance = docDbus["deviceInstance"]; + String deviceInstance = docInstance[serial_str]; + + String inverter = serial_str; + VictronDeviceInstance.insert({inverter, deviceInstance}); + + if (VictronDeviceInstance.find(inverter)!=VictronDeviceInstance.end()) { + String valfound = VictronDeviceInstance[inverter]; + Serial.print(F("Register inverter: ")); + Serial.print(serial_str); + Serial.print(F(" to Victron Venus OS with portalId: ")); + Serial.print(VictronPortalId); + Serial.print(F(" and deviceInstance: ")); + Serial.println(valfound); + } return; } - uint64_t serial; serial = strtoull(serial_str, 0, 16); auto inv = Hoymiles.getInverterBySerial(serial); if (inv == nullptr) { - Serial.println(F("Inverter not found")); + Serial.print(F("Inverter not found: ")); + Serial.println(serial); return; } @@ -228,6 +290,23 @@ bool MqttSettingsClass::getConnected() return mqttClient->connected(); } +String MqttSettingsClass::getVictronPortalId() +{ + return VictronPortalId; +} + +String MqttSettingsClass::getVictronDeviceInstance(String hoyserial) +{ + if (VictronDeviceInstance.find(hoyserial)!=VictronDeviceInstance.end()) { + return VictronDeviceInstance[hoyserial]; + } else { + Serial.print(F("No Victron deviceInstance found for inverter: ")); + Serial.println(hoyserial); + String ret = hoyserial + "NOdevInstance"; + return ret; + } +} + String MqttSettingsClass::getPrefix() { return Configuration.get().Mqtt_Topic; @@ -247,6 +326,11 @@ void MqttSettingsClass::publishHass(const String& subtopic, const String& payloa mqttClient->publish(topic.c_str(), 0, Configuration.get().Mqtt_Hass_Retain, payload.c_str()); } +void MqttSettingsClass::publishVictron(const String& topic, const String& payload) +{ + mqttClient->publish(topic.c_str(), 0, 1, payload.c_str()); +} + void MqttSettingsClass::init() { using std::placeholders::_1; diff --git a/src/MqttVictronPublishing.cpp b/src/MqttVictronPublishing.cpp new file mode 100644 index 000000000..7443ab015 --- /dev/null +++ b/src/MqttVictronPublishing.cpp @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Thomas Basler and others + */ +#include "ArduinoJson.h" +#include "MqttVictronPublishing.h" +#include "MqttPublishing.h" +#include "MqttSettings.h" +#include "NetworkSettings.h" + +MqttVictronPublishingClass MqttVictronPublishing; + +void MqttVictronPublishingClass::init() +{ +} + +void MqttVictronPublishingClass::loop() +{ + if (!Configuration.get().Mqtt_Victron_Enabled) { + // Victron stuff enabled? + return; + } + + if (!Hoymiles.getRadio()->isIdle()) { + // Hoymiles are ready to go? + return; + } + + if (MqttSettings.getConnected() && _registerForced) { + // Connection established, force register inverter @Victron Cerbo + registerInverter(); + _registerForced = false; + } + + if (MqttSettings.getConnected() && !_wasConnected) { + // Connection established, register inverter @Victron Cerbo + _wasConnected = true; + registerInverter(); + } else if (!MqttSettings.getConnected() && _wasConnected) { + // Connection lost, register again next time when connection established + _wasConnected = false; + } + + const CONFIG_T& config = Configuration.get(); + + if (millis() - _lastPublish > (config.Mqtt_PublishInterval * 1000)) { + + // Loop all inverters + for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { + auto inv = Hoymiles.getInverterByPos(i); + + String str_serial = inv->serialString(); + + uint32_t lastUpdate = inv->Statistics()->getLastUpdate(); + if (lastUpdate > 0 && lastUpdate != _lastPublishStats[i]) { + _lastPublishStats[i] = lastUpdate; + + String invname = inv->name(); + + // Get Current phase + uint8_t invphase = config.Inverter[i].CurrentPhase; + + // Loop all fields in channel 0 + for (uint8_t f = 0; f < sizeof(_publishFields); f++) { + publishField(inv, invphase, _publishFields[f]); + } + } + + yield(); + } + + _lastPublish = millis(); + } +} + +void MqttVictronPublishingClass::forceRegister() +{ + _registerForced = true; +} + +void MqttVictronPublishingClass::registerInverter() +{ + const CONFIG_T& config = Configuration.get(); + + // Loop all inverters + for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { + auto inv = Hoymiles.getInverterByPos(i); + + String str_serial = inv->serialString(); + String invname = inv->name(); + + // Get Current phase + uint8_t invphase = config.Inverter[i].CurrentPhase; + uint8_t invconnected; + + // Current phase = L0 --> No register to Victron Cerbo, only L1, L2, L3 + if (invphase == 0) { invconnected = 0; } else { invconnected = 1; } + + // Publish inverter as device service to Victron Venus OS with connected=1 + DynamicJsonDocument serviceDoc(64); + serviceDoc[str_serial] = F("pvinverter"); + JsonObject serviceObj = serviceDoc.as(); + + String Vtopic = ("device/HM" + str_serial + "/Status"); + DynamicJsonDocument rootDoc(256); + rootDoc[F("clientId")] = "HM" + str_serial; + rootDoc[F("connected")] = invconnected; + rootDoc[F("version")] = "0.1-L" + String(invphase) + "-" + invname; + rootDoc[F("services")] = serviceObj; + JsonObject rootObj = rootDoc.as(); + String data; + serializeJson(rootObj, data); + + MqttSettings.publishVictron(Vtopic, data); + } +} + +void MqttVictronPublishingClass::publishField(std::shared_ptr inv, uint8_t invphase, uint8_t fieldId) +{ + bool response = false; + String fieldname; + if ( fieldId == 4 ) { response = true; fieldname = "YieldTotal"; } + if ( fieldId == 5 ) { response = true; fieldname = "Voltage"; } + if ( fieldId == 6 ) { response = true; fieldname = "Current"; } + if ( fieldId == 7 ) { response = true; fieldname = "Power"; } + + if ( response ) { + double fieldval = double(inv->Statistics()->getChannelFieldValue(0, fieldId)); + double fieldvalue = floor( fieldval * 100.0 + .5 ) / 100.0; + + String portalid = MqttSettings.getVictronPortalId(); + if ( portalid == NULL ) { portalid = "NOportalId"; } + + String topic_sum, topic_phase, topic_err, topic_maxp; + + String invSerial = inv->serialString(); + String deviceInstance = MqttSettings.getVictronDeviceInstance(invSerial); + String topic = "W/" + portalid + "/pvinverter/" + deviceInstance; + + if ( fieldId == 4 ) { + topic_sum += topic + "/Ac/Energy/Forward"; + topic_phase += topic + "/Ac/L" + invphase + "/Energy/Forward"; + } else { + topic_sum += topic + "/Ac/" + fieldname; + topic_phase += topic + "/Ac/L" + invphase + "/" + fieldname; + } + + DynamicJsonDocument valueDoc(32); + valueDoc["value"] = fieldvalue; + JsonObject valueObj = valueDoc.as(); + String data; + serializeJson(valueObj, data); + + Serial.print(F("MqTT publish value: ")); + Serial.print(fieldId); + Serial.print(data); + Serial.print(F(" to Venus OS with topic sum: ")); + Serial.print(topic_sum); + Serial.print(F(" and topic phase: ")); + Serial.println(topic_phase); + + MqttSettings.publishVictron(topic_sum, data); + MqttSettings.publishVictron(topic_phase, data); + + // Publish limit and error code to Victron Venus OS + uint16_t maxpower = inv->DevInfo()->getMaxPower(); + topic_err += topic + "/ErrorCode"; + + DynamicJsonDocument val1Doc(32); + val1Doc["value"] = 0; + JsonObject val1Obj = val1Doc.as(); + String data1; + serializeJson(val1Obj, data1); + MqttSettings.publishVictron(topic_err, data1); + + topic_maxp += topic + "/Ac/MaxPower"; + DynamicJsonDocument val2Doc(32); + val2Doc["value"] = maxpower; + JsonObject val2Obj = val2Doc.as(); + String data2; + serializeJson(val2Obj, data2); + MqttSettings.publishVictron(topic_maxp, data2); + + // Send Value 0 to other current phases and energy forward + uint8_t nonval = 0; + + DynamicJsonDocument nonvalueDoc(32); + nonvalueDoc["value"] = nonval; + JsonObject nonvalueObj = nonvalueDoc.as(); + String nondata; + serializeJson(nonvalueObj, nondata); + + String valA, valB, topicA, topicB; + + switch ( invphase ) { + case 1: + valA = "L2"; + valB = "L3"; + break; + case 2: + valA = "L1"; + valB = "L3"; + break; + case 3: + valA = "L1"; + valB = "L2"; + break; + default: + return; + break; + } + + if ( fieldname == "YieldTotal" ) { + topicA += topic + "/Ac/" + valA + "/Energy/Forward"; + topicB += topic + "/Ac/" + valB + "/Energy/Forward"; + } else { + topicA += topic + "/Ac/" + valA + "/" + fieldname; + topicB += topic + "/Ac/" + valB + "/" + fieldname; + } + + MqttSettings.publishVictron(topicA, nondata); + MqttSettings.publishVictron(topicB, nondata); + } +} \ No newline at end of file diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index a99fe7c2f..82a9b0bdc 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -8,6 +8,7 @@ #include "Configuration.h" #include "Hoymiles.h" #include "MqttHassPublishing.h" +#include "MqttVictronPublishing.h" #include "WebApi.h" #include "helper.h" @@ -62,6 +63,8 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request) max_channels = inv->Statistics()->getChannelCount(); } + obj[F("phase")] = config.Inverter[i].CurrentPhase; + JsonArray channel = obj.createNestedArray("channel"); for (uint8_t c = 0; c < max_channels; c++) { JsonObject chanData = channel.createNestedObject(); @@ -162,6 +165,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request) } MqttHassPublishing.forceUpdate(); + MqttVictronPublishing.forceRegister(); } void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) @@ -245,6 +249,8 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) inverter.Serial = new_serial; strncpy(inverter.Name, root[F("name")].as().c_str(), INV_MAX_NAME_STRLEN); + inverter.CurrentPhase = strtoll(root[F("phase")].as().c_str(), NULL, 16); + uint8_t arrayCount = 0; for (JsonVariant channel : channelArray) { inverter.channel[arrayCount].MaxChannelPower = channel[F("max_power")].as(); @@ -281,6 +287,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) } MqttHassPublishing.forceUpdate(); + MqttVictronPublishing.forceRegister(); } void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request) @@ -349,4 +356,5 @@ void WebApiInverterClass::onInverterDelete(AsyncWebServerRequest* request) request->send(response); MqttHassPublishing.forceUpdate(); + MqttVictronPublishing.forceRegister(); } \ No newline at end of file diff --git a/src/WebApi_mqtt.cpp b/src/WebApi_mqtt.cpp index 3083851e2..92577933b 100644 --- a/src/WebApi_mqtt.cpp +++ b/src/WebApi_mqtt.cpp @@ -48,6 +48,7 @@ void WebApiMqttClass::onMqttStatus(AsyncWebServerRequest* request) root[F("mqtt_hass_retain")] = config.Mqtt_Hass_Retain; root[F("mqtt_hass_topic")] = config.Mqtt_Hass_Topic; root[F("mqtt_hass_individualpanels")] = config.Mqtt_Hass_IndividualPanels; + root[F("mqtt_victron_enabled")] = config.Mqtt_Victron_Enabled; response->setLength(); request->send(response); @@ -81,6 +82,7 @@ void WebApiMqttClass::onMqttAdminGet(AsyncWebServerRequest* request) root[F("mqtt_hass_retain")] = config.Mqtt_Hass_Retain; root[F("mqtt_hass_topic")] = config.Mqtt_Hass_Topic; root[F("mqtt_hass_individualpanels")] = config.Mqtt_Hass_IndividualPanels; + root[F("mqtt_victron_enabled")] = config.Mqtt_Victron_Enabled; response->setLength(); request->send(response); @@ -138,7 +140,8 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) && root.containsKey("mqtt_hass_expire") && root.containsKey("mqtt_hass_retain") && root.containsKey("mqtt_hass_topic") - && root.containsKey("mqtt_hass_individualpanels"))) { + && root.containsKey("mqtt_hass_individualpanels") + && root.containsKey("mqtt_victron_enabled"))) { retMsg[F("message")] = F("Values are missing!"); response->setLength(); request->send(response); @@ -271,6 +274,7 @@ void WebApiMqttClass::onMqttAdminPost(AsyncWebServerRequest* request) config.Mqtt_Hass_Retain = root[F("mqtt_hass_retain")].as(); config.Mqtt_Hass_IndividualPanels = root[F("mqtt_hass_individualpanels")].as(); strlcpy(config.Mqtt_Hass_Topic, root[F("mqtt_hass_topic")].as().c_str(), sizeof(config.Mqtt_Hass_Topic)); + config.Mqtt_Victron_Enabled = root[F("mqtt_victron_enabled")].as(); Configuration.write(); retMsg[F("type")] = F("success"); diff --git a/src/main.cpp b/src/main.cpp index 24102c19a..6e8e0d338 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include "MqttHassPublishing.h" #include "MqttPublishing.h" #include "MqttSettings.h" +#include "MqttVictronPublishing.h" #include "NetworkSettings.h" #include "NtpSettings.h" #include "Utils.h" @@ -135,6 +136,8 @@ void loop() yield(); MqttHassPublishing.loop(); yield(); + MqttVictronPublishing.loop(); + yield(); WebApi.loop(); yield(); } \ No newline at end of file diff --git a/webapp/src/types/MqttConfig.ts b/webapp/src/types/MqttConfig.ts index 8077ae7b7..1c322a3da 100644 --- a/webapp/src/types/MqttConfig.ts +++ b/webapp/src/types/MqttConfig.ts @@ -17,4 +17,5 @@ export interface MqttConfig { mqtt_hass_retain: boolean; mqtt_hass_topic: string; mqtt_hass_individualpanels: boolean; + mqtt_victron_enabled: boolean; } \ No newline at end of file diff --git a/webapp/src/types/MqttStatus.ts b/webapp/src/types/MqttStatus.ts index fe046edb2..8fab2b689 100644 --- a/webapp/src/types/MqttStatus.ts +++ b/webapp/src/types/MqttStatus.ts @@ -14,4 +14,5 @@ export interface MqttStatus { mqtt_hass_retain: boolean; mqtt_hass_topic: string; mqtt_hass_individualpanels: boolean; + mqtt_victron_enabled: boolean; } \ No newline at end of file diff --git a/webapp/src/views/InverterAdminView.vue b/webapp/src/views/InverterAdminView.vue index 981642eff..c552251bd 100644 --- a/webapp/src/views/InverterAdminView.vue +++ b/webapp/src/views/InverterAdminView.vue @@ -39,6 +39,7 @@ Serial Name Type + Phase Action @@ -47,6 +48,9 @@ {{ inverter.serial }} {{ inverter.name }} {{ inverter.type }} + + L{{ inverter.phase}} + @@ -81,6 +85,17 @@ class="form-control" maxlength="31" /> +
+ +
+ +
+
+
@@ -162,6 +177,7 @@ declare interface Inverter { serial: number; name: string; type: string; + phase: number; channel: Array; } @@ -186,6 +202,12 @@ export default defineComponent({ selectedInverterData: {} as Inverter, inverters: [] as Inverter[], dataLoading: true, + phaseList: [ + { key: 0, value: "None" }, + { key: 1, value: "L1" }, + { key: 2, value: "L2" }, + { key: 3, value: "L3" }, + ], alert: {} as AlertResponse }; }, diff --git a/webapp/src/views/MqttAdminView.vue b/webapp/src/views/MqttAdminView.vue index 769412946..cfd42abab 100644 --- a/webapp/src/views/MqttAdminView.vue +++ b/webapp/src/views/MqttAdminView.vue @@ -28,6 +28,17 @@
+ +
+ +
+
+ +
+
+
+ diff --git a/webapp_dist/index.html.gz b/webapp_dist/index.html.gz index d0a8c5248d5b683e445218e217e429462811da31..f7547917eb878c793d74d8a26e7d8fbb25464fcb 100644 GIT binary patch literal 336 zcmV-W0k8faiwFP!000023SCk?Ps1<}-I4eYhnFrihK(W4LSbNOC0ZdyH@-I4I<{rI ziSqBcCMiNJwx93(K29zlR_oX8_vQ_n+}i5?uDtGGwL2<-PVfyt*`PM#iO(%&4Rxbq z!mPw@`@Vb;Aa7Mm?hFyF5>$I9pow@;LLPK?#RV^annx<9TU4SW?1PV40JmjyOx=^o zjWWnDaXErVAV- i&r)fq12Rf*wrenkb7c%8=QJE}`T{?eA)#Ir0ssJc;G0kY literal 329 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 literal 4100 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 Ck?b-6 From 7b41588a98ba9365d9ce515a436c64ec6945afb4 Mon Sep 17 00:00:00 2001 From: stromibaer Date: Tue, 22 Nov 2022 20:50:47 +0100 Subject: [PATCH 2/5] Changes after 1st review --- include/Configuration.h | 2 +- src/MqttPublishing.cpp | 10 +-- src/MqttSettings.cpp | 9 ++- src/MqttVictronPublishing.cpp | 92 +++++++++++++++----------- webapp/src/views/InverterAdminView.vue | 4 +- 5 files changed, 64 insertions(+), 53 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 30c797179..7d5492723 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -37,7 +37,7 @@ struct CHANNEL_CONFIG_T { struct INVERTER_CONFIG_T { uint64_t Serial; char Name[INV_MAX_NAME_STRLEN + 1]; - uint16_t CurrentPhase; + uint8_t CurrentPhase; CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT]; }; diff --git a/src/MqttPublishing.cpp b/src/MqttPublishing.cpp index 51b7b1c48..6502751bd 100644 --- a/src/MqttPublishing.cpp +++ b/src/MqttPublishing.cpp @@ -148,15 +148,11 @@ void MqttPublishingClass::publishField(std::shared_ptr inv, ui String topic_Victron_sum; String topic_Victron_phase; - char serial[sizeof(uint64_t) * 8 + 1]; - snprintf(serial, sizeof(serial), "%0x%08x", - ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), - ((uint32_t)(inv->serial() & 0xFFFFFFFF))); - String invSerial = String(serial); + String invserial = inv->serialString(); - String deviceInstance = MqttSettings.getVictronDeviceInstance(serial); + String deviceInstance = MqttSettings.getVictronDeviceInstance(invserial); - int response = false; + bool response = false; if ( fieldname == "Voltage" ) { response = true; } if ( fieldname == "Power") { response = true; } if ( fieldname == "Current" ) { response = true; } diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index f19a1f2eb..bf885eff7 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -60,11 +60,7 @@ void MqttSettingsClass::onMqttConnect(bool sessionPresent) for (uint8_t i = 0; i < Hoymiles.getNumInverters(); i++) { auto inv = Hoymiles.getInverterByPos(i); - char buffer[sizeof(uint64_t) * 8 + 1]; - snprintf(buffer, sizeof(buffer), "%0x%08x", - ((uint32_t)((inv->serial() >> 32) & 0xFFFFFFFF)), - ((uint32_t)(inv->serial() & 0xFFFFFFFF))); - String str_serial = String(buffer); + String str_serial = inv->serialString(); mqttClient->subscribe(String("device/HM" + str_serial + "/DBus").c_str(),0); } } @@ -147,6 +143,9 @@ void MqttSettingsClass::onMqttMessage(const espMqttClientTypes::MessagePropertie DynamicJsonDocument docDbus(64); deserializeJson(docDbus, strlimit); + + delete[] strlimit; + VictronPortalId = docDbus["portalId"]; DynamicJsonDocument docInstance(64); diff --git a/src/MqttVictronPublishing.cpp b/src/MqttVictronPublishing.cpp index 7443ab015..ea06d7cca 100644 --- a/src/MqttVictronPublishing.cpp +++ b/src/MqttVictronPublishing.cpp @@ -2,8 +2,8 @@ /* * Copyright (C) 2022 Thomas Basler and others */ -#include "ArduinoJson.h" #include "MqttVictronPublishing.h" +#include "ArduinoJson.h" #include "MqttPublishing.h" #include "MqttSettings.h" #include "NetworkSettings.h" @@ -50,11 +50,11 @@ void MqttVictronPublishingClass::loop() auto inv = Hoymiles.getInverterByPos(i); String str_serial = inv->serialString(); - + uint32_t lastUpdate = inv->Statistics()->getLastUpdate(); if (lastUpdate > 0 && lastUpdate != _lastPublishStats[i]) { _lastPublishStats[i] = lastUpdate; - + String invname = inv->name(); // Get Current phase @@ -94,7 +94,11 @@ void MqttVictronPublishingClass::registerInverter() uint8_t invconnected; // Current phase = L0 --> No register to Victron Cerbo, only L1, L2, L3 - if (invphase == 0) { invconnected = 0; } else { invconnected = 1; } + if (invphase == 0) { + invconnected = 0; + } else { + invconnected = 1; + } // Publish inverter as device service to Victron Venus OS with connected=1 DynamicJsonDocument serviceDoc(64); @@ -110,7 +114,7 @@ void MqttVictronPublishingClass::registerInverter() JsonObject rootObj = rootDoc.as(); String data; serializeJson(rootObj, data); - + MqttSettings.publishVictron(Vtopic, data); } } @@ -119,31 +123,45 @@ void MqttVictronPublishingClass::publishField(std::shared_ptr { bool response = false; String fieldname; - if ( fieldId == 4 ) { response = true; fieldname = "YieldTotal"; } - if ( fieldId == 5 ) { response = true; fieldname = "Voltage"; } - if ( fieldId == 6 ) { response = true; fieldname = "Current"; } - if ( fieldId == 7 ) { response = true; fieldname = "Power"; } + if (fieldId == 4) { + response = true; + fieldname = "YieldTotal"; + } + if (fieldId == 5) { + response = true; + fieldname = "Voltage"; + } + if (fieldId == 6) { + response = true; + fieldname = "Current"; + } + if (fieldId == 7) { + response = true; + fieldname = "Power"; + } - if ( response ) { + if (response) { double fieldval = double(inv->Statistics()->getChannelFieldValue(0, fieldId)); - double fieldvalue = floor( fieldval * 100.0 + .5 ) / 100.0; + double fieldvalue = floor(fieldval * 100.0 + .5) / 100.0; String portalid = MqttSettings.getVictronPortalId(); - if ( portalid == NULL ) { portalid = "NOportalId"; } + if (portalid == NULL) { + portalid = "NOportalId"; + } String topic_sum, topic_phase, topic_err, topic_maxp; - + String invSerial = inv->serialString(); String deviceInstance = MqttSettings.getVictronDeviceInstance(invSerial); String topic = "W/" + portalid + "/pvinverter/" + deviceInstance; - if ( fieldId == 4 ) { + if (fieldId == 4) { topic_sum += topic + "/Ac/Energy/Forward"; topic_phase += topic + "/Ac/L" + invphase + "/Energy/Forward"; } else { topic_sum += topic + "/Ac/" + fieldname; topic_phase += topic + "/Ac/L" + invphase + "/" + fieldname; - } + } DynamicJsonDocument valueDoc(32); valueDoc["value"] = fieldvalue; @@ -192,32 +210,32 @@ void MqttVictronPublishingClass::publishField(std::shared_ptr String valA, valB, topicA, topicB; - switch ( invphase ) { - case 1: - valA = "L2"; - valB = "L3"; - break; - case 2: - valA = "L1"; - valB = "L3"; - break; - case 3: - valA = "L1"; - valB = "L2"; - break; - default: - return; - break; + switch (invphase) { + case 1: + valA = "L2"; + valB = "L3"; + break; + case 2: + valA = "L1"; + valB = "L3"; + break; + case 3: + valA = "L1"; + valB = "L2"; + break; + default: + return; + break; } - if ( fieldname == "YieldTotal" ) { - topicA += topic + "/Ac/" + valA + "/Energy/Forward"; - topicB += topic + "/Ac/" + valB + "/Energy/Forward"; + if (fieldname == "YieldTotal") { + topicA += topic + "/Ac/" + valA + "/Energy/Forward"; + topicB += topic + "/Ac/" + valB + "/Energy/Forward"; } else { - topicA += topic + "/Ac/" + valA + "/" + fieldname; - topicB += topic + "/Ac/" + valB + "/" + fieldname; + topicA += topic + "/Ac/" + valA + "/" + fieldname; + topicB += topic + "/Ac/" + valB + "/" + fieldname; } - + MqttSettings.publishVictron(topicA, nondata); MqttSettings.publishVictron(topicB, nondata); } diff --git a/webapp/src/views/InverterAdminView.vue b/webapp/src/views/InverterAdminView.vue index c552251bd..0b45b380c 100644 --- a/webapp/src/views/InverterAdminView.vue +++ b/webapp/src/views/InverterAdminView.vue @@ -48,9 +48,7 @@ {{ inverter.serial }} {{ inverter.name }} {{ inverter.type }} - - L{{ inverter.phase}} - + L{{ inverter.phase}}
From 06c9b117a1e03c0265880ba5e8e0ee291414aa90 Mon Sep 17 00:00:00 2001 From: stromibaer Date: Wed, 23 Nov 2022 13:10:34 +0100 Subject: [PATCH 3/5] Changes after review #2 --- include/MqttSettings.h | 2 +- src/Configuration.cpp | 2 +- src/MqttSettings.cpp | 4 ++-- src/WebApi_inverter.cpp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/MqttSettings.h b/include/MqttSettings.h index 61de27db3..22a4dd4aa 100644 --- a/include/MqttSettings.h +++ b/include/MqttSettings.h @@ -23,7 +23,7 @@ class MqttSettingsClass { std::map VictronDeviceInstance; String getVictronPortalId(); - String getVictronDeviceInstance(String hoyserial); + String getVictronDeviceInstance(const String& hoyserial); String getPrefix(); diff --git a/src/Configuration.cpp b/src/Configuration.cpp index ab755fea7..422fce366 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -210,7 +210,7 @@ bool ConfigurationClass::read() JsonObject inv = inverters[i].as(); config.Inverter[i].Serial = inv["serial"] | 0ULL; strlcpy(config.Inverter[i].Name, inv["name"] | "", sizeof(config.Inverter[i].Name)); - config.Inverter[i].CurrentPhase = inv["phase"] | 0ULL; + config.Inverter[i].CurrentPhase = inv["phase"] | 0; JsonArray channel = inv["channel"]; for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) { diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index bf885eff7..3b0fc2039 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -145,7 +145,7 @@ void MqttSettingsClass::onMqttMessage(const espMqttClientTypes::MessagePropertie deserializeJson(docDbus, strlimit); delete[] strlimit; - + VictronPortalId = docDbus["portalId"]; DynamicJsonDocument docInstance(64); @@ -294,7 +294,7 @@ String MqttSettingsClass::getVictronPortalId() return VictronPortalId; } -String MqttSettingsClass::getVictronDeviceInstance(String hoyserial) +String MqttSettingsClass::getVictronDeviceInstance(const String& hoyserial) { if (VictronDeviceInstance.find(hoyserial)!=VictronDeviceInstance.end()) { return VictronDeviceInstance[hoyserial]; diff --git a/src/WebApi_inverter.cpp b/src/WebApi_inverter.cpp index 82a9b0bdc..1ae3f2054 100644 --- a/src/WebApi_inverter.cpp +++ b/src/WebApi_inverter.cpp @@ -249,8 +249,8 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request) inverter.Serial = new_serial; strncpy(inverter.Name, root[F("name")].as().c_str(), INV_MAX_NAME_STRLEN); - inverter.CurrentPhase = strtoll(root[F("phase")].as().c_str(), NULL, 16); - + inverter.CurrentPhase = root[F("phase")].as(); + uint8_t arrayCount = 0; for (JsonVariant channel : channelArray) { inverter.channel[arrayCount].MaxChannelPower = channel[F("max_power")].as(); From fb666b9b5fc68714cccc3b3a8fee6f751e8d8fcc Mon Sep 17 00:00:00 2001 From: stromibaer Date: Fri, 25 Nov 2022 21:03:05 +0100 Subject: [PATCH 4/5] Removed Victron stuff from MqttPublishing.cpp --- src/MqttPublishing.cpp | 72 ------------------------------------------ 1 file changed, 72 deletions(-) diff --git a/src/MqttPublishing.cpp b/src/MqttPublishing.cpp index 6502751bd..0beb0f6f5 100644 --- a/src/MqttPublishing.cpp +++ b/src/MqttPublishing.cpp @@ -67,27 +67,6 @@ void MqttPublishingClass::loop() uint16_t maxpower = inv->DevInfo()->getMaxPower(); if (maxpower > 0) { MqttSettings.publish(subtopic + "/status/limit_absolute", String(inv->SystemConfigPara()->getLimitPercent() * maxpower / 100)); - - // Publish maxpower and error code to Victron Venus OS - String deviceInstance = MqttSettings.getVictronDeviceInstance(subtopic); - String portalid = MqttSettings.getVictronPortalId(); - if ( portalid == NULL ) { portalid = "NOportalId"; } - - String Vtopic = "W/" + portalid + "/pvinverter/" + deviceInstance + "/ErrorCode"; - DynamicJsonDocument val1Doc(128); - val1Doc["value"] = 0; - JsonObject val1Obj = val1Doc.as(); - String data1; - serializeJson(val1Obj, data1); - MqttSettings.publishVictron(Vtopic, data1); - - Vtopic = "W/" + portalid + "/pvinverter/" + deviceInstance + "/Ac/MaxPower"; - DynamicJsonDocument val2Doc(128); - val2Doc["value"] = maxpower; - JsonObject val2Obj = val2Doc.as(); - String data2; - serializeJson(val2Obj, data2); - MqttSettings.publishVictron(Vtopic, data2); } } @@ -133,57 +112,6 @@ void MqttPublishingClass::publishField(std::shared_ptr inv, ui } MqttSettings.publish(topic, String(inv->Statistics()->getChannelFieldValue(channel, fieldId))); - - // topic = "W/{}/pvinverter/{}/{}".format(portalId, deviceId, key) # UPDATE THIS - // print("{} = {}".format(topic, data.get(key) ) ) - // client.publish(topic, json.dumps({ "value": data.get(key) }) ) - if (channel == 0) { - - float fieldvalue = float(inv->Statistics()->getChannelFieldValue(channel, fieldId)); - fieldvalue = round(fieldvalue * 100)/100; - String fieldname = (inv->Statistics()->getChannelFieldName(channel, fieldId)); - String portalid = MqttSettings.getVictronPortalId(); - if ( portalid == NULL ) { portalid = "NOportalId"; } - String topic = "W/" + portalid + "/pvinverter"; - String topic_Victron_sum; - String topic_Victron_phase; - - String invserial = inv->serialString(); - - String deviceInstance = MqttSettings.getVictronDeviceInstance(invserial); - - bool response = false; - if ( fieldname == "Voltage" ) { response = true; } - if ( fieldname == "Power") { response = true; } - if ( fieldname == "Current" ) { response = true; } - if ( fieldname == "YieldTotal") { response = true; } - - if ( response ) { - // fieldname[0] = std::toupper(fieldname[0]); - if ( fieldname == "YieldTotal" ) { - topic_Victron_sum += topic + "/" + deviceInstance + "/Ac/Energy/Forward"; - topic_Victron_phase += topic + "/" + deviceInstance + "/Ac/L1/Energy/Forward"; - } else { - topic_Victron_sum += topic + "/" + deviceInstance + "/Ac/" + fieldname; - topic_Victron_phase += topic + "/" + deviceInstance + "/Ac/L1/" + fieldname; - } - - DynamicJsonDocument valueDoc(256); - valueDoc["value"] = fieldvalue; - JsonObject valueObj = valueDoc.as(); - - String data; - serializeJson(valueObj, data); - - Serial.print(F("MqTT publish value: ")); - Serial.print(data); - Serial.print(F(" to Venus OS with topic: ")); - Serial.println(topic_Victron_sum); - - MqttSettings.publishVictron(topic_Victron_sum, data); - MqttSettings.publishVictron(topic_Victron_phase, data); - } - } } String MqttPublishingClass::getTopic(std::shared_ptr inv, uint8_t channel, uint8_t fieldId) From a4a8d8674226f906a872b2c2bf5ffd6c9e681903 Mon Sep 17 00:00:00 2001 From: stromibaer Date: Fri, 25 Nov 2022 22:11:19 +0100 Subject: [PATCH 5/5] Removed some victron related libraries from MqttPublishing --- src/MqttPublishing.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/MqttPublishing.cpp b/src/MqttPublishing.cpp index 0beb0f6f5..9bc73b057 100644 --- a/src/MqttPublishing.cpp +++ b/src/MqttPublishing.cpp @@ -2,12 +2,10 @@ /* * Copyright (C) 2022 Thomas Basler and others */ -#include "ArduinoJson.h" #include "MqttPublishing.h" #include "MqttSettings.h" #include "NetworkSettings.h" #include -#include MqttPublishingClass MqttPublishing;