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.
This commit is contained in:
Bernhard Kirchen 2024-02-17 11:16:35 +01:00
parent 921302bf73
commit 30bfffb848
3 changed files with 28 additions and 16 deletions

View File

@ -20,6 +20,9 @@ class BatteryStats {
uint8_t getSoC() const { return _SoC; } uint8_t getSoC() const { return _SoC; }
uint32_t getSoCAgeSeconds() const { return (millis() - _lastUpdateSoC) / 1000; } 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 // convert stats to JSON for web application live view
virtual void getLiveViewData(JsonVariant& root) const; virtual void getLiveViewData(JsonVariant& root) const;
@ -33,6 +36,10 @@ class BatteryStats {
protected: protected:
virtual void mqttPublish() const; virtual void mqttPublish() const;
void setVoltage(float voltage, uint32_t timestamp) {
_voltage = voltage;
_lastUpdateVoltage = timestamp;
}
String _manufacturer = "unknown"; String _manufacturer = "unknown";
uint8_t _SoC = 0; uint8_t _SoC = 0;
@ -41,6 +48,8 @@ class BatteryStats {
private: private:
uint32_t _lastMqttPublish = 0; uint32_t _lastMqttPublish = 0;
float _voltage = 0; // total battery pack voltage
uint32_t _lastUpdateVoltage = 0;
}; };
class PylontechBatteryStats : public BatteryStats { class PylontechBatteryStats : public BatteryStats {
@ -59,7 +68,6 @@ class PylontechBatteryStats : public BatteryStats {
float _chargeCurrentLimitation; float _chargeCurrentLimitation;
float _dischargeCurrentLimitation; float _dischargeCurrentLimitation;
uint16_t _stateOfHealth; uint16_t _stateOfHealth;
float _voltage; // total voltage of the battery pack
// total current into (positive) or from (negative) // total current into (positive) or from (negative)
// the battery, i.e., the charging current // the battery, i.e., the charging current
float _current; float _current;
@ -123,7 +131,6 @@ class VictronSmartShuntStats : public BatteryStats {
void updateFrom(VeDirectShuntController::veShuntStruct const& shuntData); void updateFrom(VeDirectShuntController::veShuntStruct const& shuntData);
private: private:
float _voltage;
float _current; float _current;
float _temperature; float _temperature;
bool _tempPresent; bool _tempPresent;

View File

@ -57,6 +57,7 @@ void BatteryStats::getLiveViewData(JsonVariant& root) const
root[F("data_age")] = getAgeSeconds(); root[F("data_age")] = getAgeSeconds();
addLiveViewValue(root, "SoC", _SoC, "%", 0); addLiveViewValue(root, "SoC", _SoC, "%", 0);
addLiveViewValue(root, "voltage", _voltage, "V", 2);
} }
void PylontechBatteryStats::getLiveViewData(JsonVariant& root) const void PylontechBatteryStats::getLiveViewData(JsonVariant& root) const
@ -68,7 +69,6 @@ void PylontechBatteryStats::getLiveViewData(JsonVariant& root) const
addLiveViewValue(root, "chargeCurrentLimitation", _chargeCurrentLimitation, "A", 1); addLiveViewValue(root, "chargeCurrentLimitation", _chargeCurrentLimitation, "A", 1);
addLiveViewValue(root, "dischargeCurrentLimitation", _dischargeCurrentLimitation, "A", 1); addLiveViewValue(root, "dischargeCurrentLimitation", _dischargeCurrentLimitation, "A", 1);
addLiveViewValue(root, "stateOfHealth", _stateOfHealth, "%", 0); addLiveViewValue(root, "stateOfHealth", _stateOfHealth, "%", 0);
addLiveViewValue(root, "voltage", _voltage, "V", 2);
addLiveViewValue(root, "current", _current, "A", 1); addLiveViewValue(root, "current", _current, "A", 1);
addLiveViewValue(root, "temperature", _temperature, "°C", 1); addLiveViewValue(root, "temperature", _temperature, "°C", 1);
@ -105,18 +105,13 @@ void JkBmsBatteryStats::getJsonData(JsonVariant& root, bool verbose) const
using Label = JkBms::DataPointLabel; using Label = JkBms::DataPointLabel;
auto oVoltage = _dataPoints.get<Label::BatteryVoltageMilliVolt>();
if (oVoltage.has_value()) {
addLiveViewValue(root, "voltage",
static_cast<float>(*oVoltage) / 1000, "V", 2);
}
auto oCurrent = _dataPoints.get<Label::BatteryCurrentMilliAmps>(); auto oCurrent = _dataPoints.get<Label::BatteryCurrentMilliAmps>();
if (oCurrent.has_value()) { if (oCurrent.has_value()) {
addLiveViewValue(root, "current", addLiveViewValue(root, "current",
static_cast<float>(*oCurrent) / 1000, "A", 2); static_cast<float>(*oCurrent) / 1000, "A", 2);
} }
auto oVoltage = _dataPoints.get<Label::BatteryVoltageMilliVolt>();
if (oVoltage.has_value() && oCurrent.has_value()) { if (oVoltage.has_value() && oCurrent.has_value()) {
auto current = static_cast<float>(*oCurrent) / 1000; auto current = static_cast<float>(*oCurrent) / 1000;
auto voltage = static_cast<float>(*oVoltage) / 1000; auto voltage = static_cast<float>(*oVoltage) / 1000;
@ -218,6 +213,7 @@ void BatteryStats::mqttPublish() const
MqttSettings.publish(F("battery/manufacturer"), _manufacturer); MqttSettings.publish(F("battery/manufacturer"), _manufacturer);
MqttSettings.publish(F("battery/dataAge"), String(getAgeSeconds())); MqttSettings.publish(F("battery/dataAge"), String(getAgeSeconds()));
MqttSettings.publish(F("battery/stateOfCharge"), String(_SoC)); MqttSettings.publish(F("battery/stateOfCharge"), String(_SoC));
MqttSettings.publish(F("battery/voltage"), String(_voltage));
} }
void PylontechBatteryStats::mqttPublish() const 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/chargeCurrentLimitation"), String(_chargeCurrentLimitation));
MqttSettings.publish(F("battery/settings/dischargeCurrentLimitation"), String(_dischargeCurrentLimitation)); MqttSettings.publish(F("battery/settings/dischargeCurrentLimitation"), String(_dischargeCurrentLimitation));
MqttSettings.publish(F("battery/stateOfHealth"), String(_stateOfHealth)); 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/current"), String(_current));
MqttSettings.publish(F("battery/temperature"), String(_temperature)); MqttSettings.publish(F("battery/temperature"), String(_temperature));
MqttSettings.publish(F("battery/alarm/overCurrentDischarge"), String(_alarmOverCurrentDischarge)); MqttSettings.publish(F("battery/alarm/overCurrentDischarge"), String(_alarmOverCurrentDischarge));
@ -260,6 +255,10 @@ void JkBmsBatteryStats::mqttPublish() const
Label::CellsMilliVolt, // complex data format Label::CellsMilliVolt, // complex data format
Label::ModificationPassword, // sensitive data Label::ModificationPassword, // sensitive data
Label::BatterySoCPercent // already published by base class 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 // 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(); _lastUpdateSoC = oSoCDataPoint->getTimestamp();
} }
auto oVoltage = dp.get<Label::BatteryVoltageMilliVolt>();
if (oVoltage.has_value()) {
auto oVoltageDataPoint = dp.getDataPointFor<Label::BatteryVoltageMilliVolt>();
BatteryStats::setVoltage(static_cast<float>(*oVoltage) / 1000,
oVoltageDataPoint->getTimestamp());
}
_dataPoints.updateFrom(dp); _dataPoints.updateFrom(dp);
auto oCellVoltages = _dataPoints.get<Label::CellsMilliVolt>(); auto oCellVoltages = _dataPoints.get<Label::CellsMilliVolt>();
@ -360,8 +366,9 @@ void JkBmsBatteryStats::updateFrom(JkBms::DataPointContainer const& dp)
} }
void VictronSmartShuntStats::updateFrom(VeDirectShuntController::veShuntStruct const& shuntData) { void VictronSmartShuntStats::updateFrom(VeDirectShuntController::veShuntStruct const& shuntData) {
BatteryStats::setVoltage(shuntData.V, millis());
_SoC = shuntData.SOC / 10; _SoC = shuntData.SOC / 10;
_voltage = shuntData.V;
_current = shuntData.I; _current = shuntData.I;
_modelName = shuntData.getPidAsString().data(); _modelName = shuntData.getPidAsString().data();
_chargeCycles = shuntData.H4; _chargeCycles = shuntData.H4;
@ -387,7 +394,6 @@ void VictronSmartShuntStats::getLiveViewData(JsonVariant& root) const {
BatteryStats::getLiveViewData(root); BatteryStats::getLiveViewData(root);
// values go into the "Status" card of the web application // values go into the "Status" card of the web application
addLiveViewValue(root, "voltage", _voltage, "V", 2);
addLiveViewValue(root, "current", _current, "A", 1); addLiveViewValue(root, "current", _current, "A", 1);
addLiveViewValue(root, "chargeCycles", _chargeCycles, "", 0); addLiveViewValue(root, "chargeCycles", _chargeCycles, "", 0);
addLiveViewValue(root, "chargedEnergy", _chargedEnergy, "KWh", 1); addLiveViewValue(root, "chargedEnergy", _chargedEnergy, "KWh", 1);
@ -406,7 +412,6 @@ void VictronSmartShuntStats::getLiveViewData(JsonVariant& root) const {
void VictronSmartShuntStats::mqttPublish() const { void VictronSmartShuntStats::mqttPublish() const {
BatteryStats::mqttPublish(); BatteryStats::mqttPublish();
MqttSettings.publish(F("battery/voltage"), String(_voltage));
MqttSettings.publish(F("battery/current"), String(_current)); MqttSettings.publish(F("battery/current"), String(_current));
MqttSettings.publish(F("battery/chargeCycles"), String(_chargeCycles)); MqttSettings.publish(F("battery/chargeCycles"), String(_chargeCycles));
MqttSettings.publish(F("battery/chargedEnergy"), String(_chargedEnergy)); MqttSettings.publish(F("battery/chargedEnergy"), String(_chargedEnergy));

View File

@ -147,13 +147,13 @@ void PylontechCanReceiver::loop()
} }
case 0x356: { 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->_current = this->scaleValue(this->readSignedInt16(rx_message.data + 2), 0.1);
_stats->_temperature = this->scaleValue(this->readSignedInt16(rx_message.data + 4), 0.1); _stats->_temperature = this->scaleValue(this->readSignedInt16(rx_message.data + 4), 0.1);
if (_verboseLogging) { if (_verboseLogging) {
MessageOutput.printf("[Pylontech] voltage: %f current: %f temperature: %f\n", MessageOutput.printf("[Pylontech] voltage: %f current: %f temperature: %f\n",
_stats->_voltage, _stats->_current, _stats->_temperature); _stats->getVoltage(), _stats->_current, _stats->_temperature);
} }
break; break;
} }
@ -287,7 +287,7 @@ void PylontechCanReceiver::dummyData()
_stats->_chargeCurrentLimitation = dummyFloat(33); _stats->_chargeCurrentLimitation = dummyFloat(33);
_stats->_dischargeCurrentLimitation = dummyFloat(12); _stats->_dischargeCurrentLimitation = dummyFloat(12);
_stats->_stateOfHealth = 99; _stats->_stateOfHealth = 99;
_stats->_voltage = 48.67; _stats->setVoltage(48.67, millis());
_stats->_current = dummyFloat(-1); _stats->_current = dummyFloat(-1);
_stats->_temperature = dummyFloat(20); _stats->_temperature = dummyFloat(20);