From 30bfffb848a8043d2eb9c48ba5594191f6aa5393 Mon Sep 17 00:00:00 2001 From: Bernhard Kirchen Date: Sat, 17 Feb 2024 11:16:35 +0100 Subject: [PATCH] BatteryStats: manage battery pack voltage in base class the BatteryStats base class shall be able to tell the total battery pack voltage. for that reason, and to avoid code duplication, the voltage is now handled in the base class and treated as a datum that is common to all battery providers. --- include/BatteryStats.h | 11 +++++++++-- src/BatteryStats.cpp | 27 ++++++++++++++++----------- src/PylontechCanReceiver.cpp | 6 +++--- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/include/BatteryStats.h b/include/BatteryStats.h index 36eed06a..47c22e72 100644 --- a/include/BatteryStats.h +++ b/include/BatteryStats.h @@ -20,6 +20,9 @@ class BatteryStats { uint8_t getSoC() const { return _SoC; } uint32_t getSoCAgeSeconds() const { return (millis() - _lastUpdateSoC) / 1000; } + float getVoltage() const { return _voltage; } + uint32_t getVoltageAgeSeconds() const { return (millis() - _lastUpdateVoltage) / 1000; } + // convert stats to JSON for web application live view virtual void getLiveViewData(JsonVariant& root) const; @@ -33,6 +36,10 @@ class BatteryStats { protected: virtual void mqttPublish() const; + void setVoltage(float voltage, uint32_t timestamp) { + _voltage = voltage; + _lastUpdateVoltage = timestamp; + } String _manufacturer = "unknown"; uint8_t _SoC = 0; @@ -41,6 +48,8 @@ class BatteryStats { private: uint32_t _lastMqttPublish = 0; + float _voltage = 0; // total battery pack voltage + uint32_t _lastUpdateVoltage = 0; }; class PylontechBatteryStats : public BatteryStats { @@ -59,7 +68,6 @@ class PylontechBatteryStats : public BatteryStats { float _chargeCurrentLimitation; float _dischargeCurrentLimitation; uint16_t _stateOfHealth; - float _voltage; // total voltage of the battery pack // total current into (positive) or from (negative) // the battery, i.e., the charging current float _current; @@ -123,7 +131,6 @@ class VictronSmartShuntStats : public BatteryStats { void updateFrom(VeDirectShuntController::veShuntStruct const& shuntData); private: - float _voltage; float _current; float _temperature; bool _tempPresent; diff --git a/src/BatteryStats.cpp b/src/BatteryStats.cpp index 606a372f..976694bf 100644 --- a/src/BatteryStats.cpp +++ b/src/BatteryStats.cpp @@ -57,6 +57,7 @@ void BatteryStats::getLiveViewData(JsonVariant& root) const root[F("data_age")] = getAgeSeconds(); addLiveViewValue(root, "SoC", _SoC, "%", 0); + addLiveViewValue(root, "voltage", _voltage, "V", 2); } void PylontechBatteryStats::getLiveViewData(JsonVariant& root) const @@ -68,7 +69,6 @@ void PylontechBatteryStats::getLiveViewData(JsonVariant& root) const addLiveViewValue(root, "chargeCurrentLimitation", _chargeCurrentLimitation, "A", 1); addLiveViewValue(root, "dischargeCurrentLimitation", _dischargeCurrentLimitation, "A", 1); addLiveViewValue(root, "stateOfHealth", _stateOfHealth, "%", 0); - addLiveViewValue(root, "voltage", _voltage, "V", 2); addLiveViewValue(root, "current", _current, "A", 1); addLiveViewValue(root, "temperature", _temperature, "°C", 1); @@ -105,18 +105,13 @@ void JkBmsBatteryStats::getJsonData(JsonVariant& root, bool verbose) const using Label = JkBms::DataPointLabel; - auto oVoltage = _dataPoints.get(); - if (oVoltage.has_value()) { - addLiveViewValue(root, "voltage", - static_cast(*oVoltage) / 1000, "V", 2); - } - auto oCurrent = _dataPoints.get(); if (oCurrent.has_value()) { addLiveViewValue(root, "current", static_cast(*oCurrent) / 1000, "A", 2); } + auto oVoltage = _dataPoints.get(); if (oVoltage.has_value() && oCurrent.has_value()) { auto current = static_cast(*oCurrent) / 1000; auto voltage = static_cast(*oVoltage) / 1000; @@ -218,6 +213,7 @@ void BatteryStats::mqttPublish() const MqttSettings.publish(F("battery/manufacturer"), _manufacturer); MqttSettings.publish(F("battery/dataAge"), String(getAgeSeconds())); MqttSettings.publish(F("battery/stateOfCharge"), String(_SoC)); + MqttSettings.publish(F("battery/voltage"), String(_voltage)); } void PylontechBatteryStats::mqttPublish() const @@ -228,7 +224,6 @@ void PylontechBatteryStats::mqttPublish() const MqttSettings.publish(F("battery/settings/chargeCurrentLimitation"), String(_chargeCurrentLimitation)); MqttSettings.publish(F("battery/settings/dischargeCurrentLimitation"), String(_dischargeCurrentLimitation)); MqttSettings.publish(F("battery/stateOfHealth"), String(_stateOfHealth)); - MqttSettings.publish(F("battery/voltage"), String(_voltage)); MqttSettings.publish(F("battery/current"), String(_current)); MqttSettings.publish(F("battery/temperature"), String(_temperature)); MqttSettings.publish(F("battery/alarm/overCurrentDischarge"), String(_alarmOverCurrentDischarge)); @@ -260,6 +255,10 @@ void JkBmsBatteryStats::mqttPublish() const Label::CellsMilliVolt, // complex data format Label::ModificationPassword, // sensitive data Label::BatterySoCPercent // already published by base class + // NOTE that voltage is also published by the base class, however, we + // previously published it only from here using the respective topic. + // to avoid a breaking change, we publish the value again using the + // "old" topic. }; // regularly publish all topics regardless of whether or not their value changed @@ -340,6 +339,13 @@ void JkBmsBatteryStats::updateFrom(JkBms::DataPointContainer const& dp) _lastUpdateSoC = oSoCDataPoint->getTimestamp(); } + auto oVoltage = dp.get(); + if (oVoltage.has_value()) { + auto oVoltageDataPoint = dp.getDataPointFor(); + BatteryStats::setVoltage(static_cast(*oVoltage) / 1000, + oVoltageDataPoint->getTimestamp()); + } + _dataPoints.updateFrom(dp); auto oCellVoltages = _dataPoints.get(); @@ -360,8 +366,9 @@ void JkBmsBatteryStats::updateFrom(JkBms::DataPointContainer const& dp) } void VictronSmartShuntStats::updateFrom(VeDirectShuntController::veShuntStruct const& shuntData) { + BatteryStats::setVoltage(shuntData.V, millis()); + _SoC = shuntData.SOC / 10; - _voltage = shuntData.V; _current = shuntData.I; _modelName = shuntData.getPidAsString().data(); _chargeCycles = shuntData.H4; @@ -387,7 +394,6 @@ void VictronSmartShuntStats::getLiveViewData(JsonVariant& root) const { BatteryStats::getLiveViewData(root); // values go into the "Status" card of the web application - addLiveViewValue(root, "voltage", _voltage, "V", 2); addLiveViewValue(root, "current", _current, "A", 1); addLiveViewValue(root, "chargeCycles", _chargeCycles, "", 0); addLiveViewValue(root, "chargedEnergy", _chargedEnergy, "KWh", 1); @@ -406,7 +412,6 @@ void VictronSmartShuntStats::getLiveViewData(JsonVariant& root) const { void VictronSmartShuntStats::mqttPublish() const { BatteryStats::mqttPublish(); - MqttSettings.publish(F("battery/voltage"), String(_voltage)); MqttSettings.publish(F("battery/current"), String(_current)); MqttSettings.publish(F("battery/chargeCycles"), String(_chargeCycles)); MqttSettings.publish(F("battery/chargedEnergy"), String(_chargedEnergy)); diff --git a/src/PylontechCanReceiver.cpp b/src/PylontechCanReceiver.cpp index c1b26176..8091f1df 100644 --- a/src/PylontechCanReceiver.cpp +++ b/src/PylontechCanReceiver.cpp @@ -147,13 +147,13 @@ void PylontechCanReceiver::loop() } case 0x356: { - _stats->_voltage = this->scaleValue(this->readSignedInt16(rx_message.data), 0.01); + _stats->setVoltage(this->scaleValue(this->readSignedInt16(rx_message.data), 0.01), millis()); _stats->_current = this->scaleValue(this->readSignedInt16(rx_message.data + 2), 0.1); _stats->_temperature = this->scaleValue(this->readSignedInt16(rx_message.data + 4), 0.1); if (_verboseLogging) { MessageOutput.printf("[Pylontech] voltage: %f current: %f temperature: %f\n", - _stats->_voltage, _stats->_current, _stats->_temperature); + _stats->getVoltage(), _stats->_current, _stats->_temperature); } break; } @@ -287,7 +287,7 @@ void PylontechCanReceiver::dummyData() _stats->_chargeCurrentLimitation = dummyFloat(33); _stats->_dischargeCurrentLimitation = dummyFloat(12); _stats->_stateOfHealth = 99; - _stats->_voltage = 48.67; + _stats->setVoltage(48.67, millis()); _stats->_current = dummyFloat(-1); _stats->_temperature = dummyFloat(20);