From 683912fa3cea1107e309994486d00976651a8b98 Mon Sep 17 00:00:00 2001 From: SW-Nico Date: Fri, 1 Nov 2024 16:40:55 +0100 Subject: [PATCH 1/2] add optional "load current" to live view, MQTT and HASS. change "load output" to optional handling --- lib/VeDirectFrameHandler/VeDirectData.h | 10 ++++++++-- .../VeDirectMpptController.cpp | 20 ++++++++++++------- src/MqttHandleVedirect.cpp | 3 ++- src/MqttHandleVedirectHass.cpp | 9 ++++++++- src/WebApi_ws_vedirect_live.cpp | 9 ++++++++- webapp/src/locales/de.json | 1 + webapp/src/locales/en.json | 1 + webapp/src/locales/fr.json | 1 + 8 files changed, 42 insertions(+), 12 deletions(-) diff --git a/lib/VeDirectFrameHandler/VeDirectData.h b/lib/VeDirectFrameHandler/VeDirectData.h index 9cd97ad61..1a06d1014 100644 --- a/lib/VeDirectFrameHandler/VeDirectData.h +++ b/lib/VeDirectFrameHandler/VeDirectData.h @@ -26,8 +26,6 @@ struct veMpptStruct : veStruct { uint32_t panelVoltage_VPV_mV; // panel voltage in mV uint32_t panelCurrent_mA; // panel current in mA (calculated) int16_t batteryOutputPower_W; // battery output power in W (calculated, can be negative if load output is used) - uint32_t loadCurrent_IL_mA; // Load current in mA (Available only for models with a load output) - bool loadOutputState_LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit) uint8_t currentState_CS; // current state of operation e.g. OFF or Bulk uint8_t errorCode_ERR; // error code uint32_t offReason_OR; // off reason @@ -38,6 +36,14 @@ struct veMpptStruct : veStruct { uint32_t yieldYesterday_H22_Wh; // yield yesterday Wh uint16_t maxPowerYesterday_H23_W; // maximum power yesterday W + // these are optional values communicated through the TEXT protocol. the pair's first + // value is the timestamp the respective info was last received. if it is + // zero, the value is deemed invalid. the timestamp is reset if no current + // value could be retrieved. + std::pair loadOutputState_LOAD; // physical load output or virtual load output state (on if battery voltage + // reaches upper limit, off if battery reaches lower limit) + std::pair loadCurrent_IL_mA; // Load current in mA (Available only for models with a physical load output) + // these are values communicated through the HEX protocol. the pair's first // value is the timestamp the respective info was last received. if it is // zero, the value is deemed invalid. the timestamp is reset if no current diff --git a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp index 6c7af8902..08480ffde 100644 --- a/lib/VeDirectFrameHandler/VeDirectMpptController.cpp +++ b/lib/VeDirectFrameHandler/VeDirectMpptController.cpp @@ -22,11 +22,13 @@ void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool VeDirectMpptController::processTextDataDerived(std::string const& name, std::string const& value) { if (name == "IL") { - _tmpFrame.loadCurrent_IL_mA = atol(value.c_str()); + _tmpFrame.loadCurrent_IL_mA.second = atol(value.c_str()); + _tmpFrame.loadCurrent_IL_mA.first = millis(); return true; } if (name == "LOAD") { - _tmpFrame.loadOutputState_LOAD = (value == "ON"); + _tmpFrame.loadOutputState_LOAD.second = (value == "ON"); + _tmpFrame.loadOutputState_LOAD.first = millis(); return true; } if (name == "CS") { @@ -97,7 +99,8 @@ void VeDirectMpptController::frameValidEvent() { } // calculation of the MPPT efficiency - float totalPower_W = (_tmpFrame.loadCurrent_IL_mA / 1000.0f + _tmpFrame.batteryCurrent_I_mA / 1000.0f) * _tmpFrame.batteryVoltage_V_mV /1000.0f; + float loadCurrent = (_tmpFrame.loadCurrent_IL_mA.first > 0) ? _tmpFrame.loadCurrent_IL_mA.second / 1000.0f : 0.0f; + float totalPower_W = (loadCurrent + _tmpFrame.batteryCurrent_I_mA / 1000.0f) * _tmpFrame.batteryVoltage_V_mV / 1000.0f; if (_tmpFrame.panelPower_PPV_W > 0) { _efficiency.addNumber(totalPower_W * 100.0f / _tmpFrame.panelPower_PPV_W); _tmpFrame.mpptEfficiency_Percent = _efficiency.getAverage(); @@ -117,16 +120,19 @@ void VeDirectMpptController::loop() // Second we read Text- and HEX-Messages VeDirectFrameHandler::loop(); - // Third we check if HEX-Data is outdated // Note: Room for improvement, longer data valid time for slow changing values? - if (!isHexCommandPossible()) { return; } - auto resetTimestamp = [this](auto& pair) { if (pair.first > 0 && (millis() - pair.first) > (10 * 1000)) { pair.first = 0; } }; + // Check if optional TEXT-Data is outdated + resetTimestamp(_tmpFrame.loadOutputState_LOAD); + resetTimestamp(_tmpFrame.loadCurrent_IL_mA); + + // Third we check if HEX-Data is outdated + if (!isHexCommandPossible()) { return; } resetTimestamp(_tmpFrame.MpptTemperatureMilliCelsius); resetTimestamp(_tmpFrame.SmartBatterySenseTemperatureMilliCelsius); resetTimestamp(_tmpFrame.NetworkTotalDcInputPowerMilliWatts); @@ -337,4 +343,4 @@ void VeDirectMpptController::sendNextHexCommandFromQueue(void) { prio = false; // second loop for low prio commands } } -} \ No newline at end of file +} diff --git a/src/MqttHandleVedirect.cpp b/src/MqttHandleVedirect.cpp index 009519dd7..d174a43be 100644 --- a/src/MqttHandleVedirect.cpp +++ b/src/MqttHandleVedirect.cpp @@ -111,7 +111,6 @@ void MqttHandleVedirectClass::publish_mppt_data(const VeDirectMpptController::da PUBLISH(productID_PID, "PID", currentData.getPidAsString().data()); PUBLISH(serialNr_SER, "SER", currentData.serialNr_SER); PUBLISH(firmwareVer_FW, "FW", currentData.firmwareVer_FW); - PUBLISH(loadOutputState_LOAD, "LOAD", (currentData.loadOutputState_LOAD ? "ON" : "OFF")); PUBLISH(currentState_CS, "CS", currentData.getCsAsString().data()); PUBLISH(errorCode_ERR, "ERR", currentData.getErrAsString().data()); PUBLISH(offReason_OR, "OR", currentData.getOrAsString().data()); @@ -136,6 +135,8 @@ void MqttHandleVedirectClass::publish_mppt_data(const VeDirectMpptController::da MqttSettings.publish(topic + t, String(val)); \ } + PUBLISH_OPT(loadOutputState_LOAD, "LOAD", currentData.loadOutputState_LOAD.second ? "ON" : "OFF"); + PUBLISH_OPT(loadCurrent_IL_mA, "IL", currentData.loadCurrent_IL_mA.second / 1000.0); PUBLISH_OPT(NetworkTotalDcInputPowerMilliWatts, "NetworkTotalDcInputPower", currentData.NetworkTotalDcInputPowerMilliWatts.second / 1000.0); PUBLISH_OPT(MpptTemperatureMilliCelsius, "MpptTemperature", currentData.MpptTemperatureMilliCelsius.second / 1000.0); PUBLISH_OPT(BatteryAbsorptionMilliVolt, "BatteryAbsorption", currentData.BatteryAbsorptionMilliVolt.second / 1000.0); diff --git a/src/MqttHandleVedirectHass.cpp b/src/MqttHandleVedirectHass.cpp index 1ea1c8066..f8cbcaae8 100644 --- a/src/MqttHandleVedirectHass.cpp +++ b/src/MqttHandleVedirectHass.cpp @@ -63,7 +63,6 @@ void MqttHandleVedirectHassClass::publishConfig() auto optMpptData = VictronMppt.getData(idx); if (!optMpptData.has_value()) { continue; } - publishBinarySensor("MPPT load output state", "mdi:export", "LOAD", "ON", "OFF", *optMpptData); publishSensor("MPPT serial number", "mdi:counter", "SER", nullptr, nullptr, nullptr, *optMpptData); publishSensor("MPPT firmware number", "mdi:counter", "FW", nullptr, nullptr, nullptr, *optMpptData); publishSensor("MPPT state of operation", "mdi:wrench", "CS", nullptr, nullptr, nullptr, *optMpptData); @@ -88,6 +87,14 @@ void MqttHandleVedirectHassClass::publishConfig() publishSensor("Panel yield yesterday", NULL, "H22", "energy", "total", "kWh", *optMpptData); publishSensor("Panel maximum power yesterday", NULL, "H23", "power", "measurement", "W", *optMpptData); + // optional info, provided only if the charge controller delivers the information + if (optMpptData->loadOutputState_LOAD.first != 0) { + publishBinarySensor("MPPT load output state", "mdi:export", "LOAD", "ON", "OFF", *optMpptData); + } + if (optMpptData->loadCurrent_IL_mA.first != 0) { + publishSensor("MPPT load current", NULL, "IL", "current", "measurement", "A", *optMpptData); + } + // optional info, provided only if TX is connected to charge controller if (optMpptData->NetworkTotalDcInputPowerMilliWatts.first != 0) { publishSensor("VE.Smart network total DC input power", "mdi:solar-power", "NetworkTotalDcInputPower", "power", "measurement", "W", *optMpptData); diff --git a/src/WebApi_ws_vedirect_live.cpp b/src/WebApi_ws_vedirect_live.cpp index 75b332a81..121d1583a 100644 --- a/src/WebApi_ws_vedirect_live.cpp +++ b/src/WebApi_ws_vedirect_live.cpp @@ -164,7 +164,14 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir const JsonObject values = root["values"].to(); const JsonObject device = values["device"].to(); - device["LOAD"] = mpptData.loadOutputState_LOAD ? "ON" : "OFF"; + if (mpptData.loadOutputState_LOAD.first > 0) { + device["LOAD"] = mpptData.loadOutputState_LOAD.second ? "ON" : "OFF"; + } + if (mpptData.loadCurrent_IL_mA.first > 0) { + device["LOADCurrent"]["v"] = mpptData.loadCurrent_IL_mA.second / 1000.0; + device["LOADCurrent"]["u"] = "A"; + device["LOADCurrent"]["d"] = 2; + } device["CS"] = mpptData.getCsAsString(); device["MPPT"] = mpptData.getMpptAsString(); device["OR"] = mpptData.getOrAsString(); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index c1f7f38c6..752201d04 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -175,6 +175,7 @@ "section_device": "Geräteinformation", "device": { "LOAD": "Status Lastausgang", + "LOADCurrent": "Laststrom", "CS": "Betriebszustand", "MPPT": "Betriebszustand des Trackers", "OR": "Grund für das Ausschalten", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 4be6ea31f..83caf679f 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -175,6 +175,7 @@ "section_device": "Device Info", "device": { "LOAD": "Load output state", + "LOADCurrent": "Load current", "CS": "State of operation", "MPPT": "Tracker operation mode", "OR": "Off reason", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index e1f110a0a..fd3e50ab0 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -175,6 +175,7 @@ "section_device": "Device Info", "device": { "LOAD": "Load output state", + "LOADCurrent": "Load current", "CS": "State of operation", "MPPT": "Tracker operation mode", "OR": "Off reason", From 5c6045b79832647a73c932ae7b2f9800a9a46fce Mon Sep 17 00:00:00 2001 From: SW-Nico Date: Sun, 3 Nov 2024 18:08:39 +0100 Subject: [PATCH 2/2] webview "Virtual load output state" if LOAD=true and IL=false --- src/WebApi_ws_vedirect_live.cpp | 14 ++++++++++---- webapp/src/locales/de.json | 3 ++- webapp/src/locales/en.json | 3 ++- webapp/src/locales/fr.json | 3 ++- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/WebApi_ws_vedirect_live.cpp b/src/WebApi_ws_vedirect_live.cpp index 121d1583a..4a56646e0 100644 --- a/src/WebApi_ws_vedirect_live.cpp +++ b/src/WebApi_ws_vedirect_live.cpp @@ -164,13 +164,19 @@ void WebApiWsVedirectLiveClass::populateJson(const JsonObject &root, const VeDir const JsonObject values = root["values"].to(); const JsonObject device = values["device"].to(); + + // LOAD IL UI label result + // ------------------------------------ + // false false Do not display LOAD and IL (device has no physical load output and virtual load is not configured) + // true false "VIRTLOAD" We display just LOAD (device has no physical load output and virtual load is configured) + // true true "LOAD" We display LOAD and IL (device has physical load output, regardless if virtual load is configured or not) if (mpptData.loadOutputState_LOAD.first > 0) { - device["LOAD"] = mpptData.loadOutputState_LOAD.second ? "ON" : "OFF"; + device[(mpptData.loadCurrent_IL_mA.first > 0) ? "LOAD" : "VIRTLOAD"] = mpptData.loadOutputState_LOAD.second ? "ON" : "OFF"; } if (mpptData.loadCurrent_IL_mA.first > 0) { - device["LOADCurrent"]["v"] = mpptData.loadCurrent_IL_mA.second / 1000.0; - device["LOADCurrent"]["u"] = "A"; - device["LOADCurrent"]["d"] = 2; + device["IL"]["v"] = mpptData.loadCurrent_IL_mA.second / 1000.0; + device["IL"]["u"] = "A"; + device["IL"]["d"] = 2; } device["CS"] = mpptData.getCsAsString(); device["MPPT"] = mpptData.getMpptAsString(); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 752201d04..dc343c671 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -175,7 +175,8 @@ "section_device": "Geräteinformation", "device": { "LOAD": "Status Lastausgang", - "LOADCurrent": "Laststrom", + "VIRTLOAD": "Status virtueller Lastausgang", + "IL": "Laststrom", "CS": "Betriebszustand", "MPPT": "Betriebszustand des Trackers", "OR": "Grund für das Ausschalten", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 83caf679f..ab2e864c9 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -175,7 +175,8 @@ "section_device": "Device Info", "device": { "LOAD": "Load output state", - "LOADCurrent": "Load current", + "VIRTLOAD": "Virtual load output state", + "IL": "Load current", "CS": "State of operation", "MPPT": "Tracker operation mode", "OR": "Off reason", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index fd3e50ab0..c5ca108bf 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -175,7 +175,8 @@ "section_device": "Device Info", "device": { "LOAD": "Load output state", - "LOADCurrent": "Load current", + "VIRTLOAD": "Virtual load output state", + "IL": "Load current", "CS": "State of operation", "MPPT": "Tracker operation mode", "OR": "Off reason",