the DPL is interested in the battery's voltage to make decisions about draining the battery or letting it charge (if the user opts to use voltage thresholds rather than SoC thresholds). using the DC input voltage reported by the inverter under control has disadvantages: * the data might be quite old due to the communication protocol implementation. more inverters being polled means even more lag. the connection being wireless makes this even worse, due to the need to retry the occasional lost packet, etc. * the data is not very accurate, since the DC input of the inverter is actually some cabling and a couple of junctions away from the actual battery. this voltage drop can mostly only be estimated and is worse with higher load. the load correction factor is there to mitigate this, but it has its own problems and is cumbersome to calibrate. instead, this change aims to use more accurate battery voltage readings, if possible. the DPL now prefers the voltage as reported by the BMS, since it is for sure the closest to the battery of all measuring points and measures its voltage accurately regardless of the load (the voltage reading will still drop with higher loads, but this will be only due to the battery's internal resistance, not that of cabling or junctions). if no BMS voltage reading is available, the DPL will instead use the charge controller's voltage reading, as it is available with much higher frequency and is assumed to be more accurate as it offers a resolution of 10mV. only if none of these two sources can be used, the inverter DC input voltage is assumed as the battery voltage. closes #655.
170 lines
5.3 KiB
C++
170 lines
5.3 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
#pragma once
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "AsyncJson.h"
|
|
#include "Arduino.h"
|
|
#include "JkBmsDataPoints.h"
|
|
#include "VeDirectShuntController.h"
|
|
|
|
// mandatory interface for all kinds of batteries
|
|
class BatteryStats {
|
|
public:
|
|
String const& getManufacturer() const { return _manufacturer; }
|
|
|
|
// the last time *any* datum was updated
|
|
uint32_t getAgeSeconds() const { return (millis() - _lastUpdate) / 1000; }
|
|
bool updateAvailable(uint32_t since) const { return _lastUpdate > since; }
|
|
|
|
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;
|
|
|
|
void mqttLoop();
|
|
|
|
// the interval at which all battery datums will be re-published, even
|
|
// if they did not change. used to calculate Home Assistent expiration.
|
|
virtual uint32_t getMqttFullPublishIntervalMs() const;
|
|
|
|
bool isSoCValid() const { return _lastUpdateSoC > 0; }
|
|
bool isVoltageValid() const { return _lastUpdateVoltage > 0; }
|
|
|
|
protected:
|
|
virtual void mqttPublish() const;
|
|
|
|
void setSoC(float soc, uint8_t precision, uint32_t timestamp) {
|
|
_soc = soc;
|
|
_socPrecision = precision;
|
|
_lastUpdateSoC = timestamp;
|
|
}
|
|
|
|
void setVoltage(float voltage, uint32_t timestamp) {
|
|
_voltage = voltage;
|
|
_lastUpdateVoltage = timestamp;
|
|
}
|
|
|
|
String _manufacturer = "unknown";
|
|
uint32_t _lastUpdate = 0;
|
|
|
|
private:
|
|
uint32_t _lastMqttPublish = 0;
|
|
float _soc = 0;
|
|
uint8_t _socPrecision = 0; // decimal places
|
|
uint32_t _lastUpdateSoC = 0;
|
|
float _voltage = 0; // total battery pack voltage
|
|
uint32_t _lastUpdateVoltage = 0;
|
|
};
|
|
|
|
class PylontechBatteryStats : public BatteryStats {
|
|
friend class PylontechCanReceiver;
|
|
|
|
public:
|
|
void getLiveViewData(JsonVariant& root) const final;
|
|
void mqttPublish() const final;
|
|
|
|
private:
|
|
void setManufacturer(String&& m) { _manufacturer = std::move(m); }
|
|
void setLastUpdate(uint32_t ts) { _lastUpdate = ts; }
|
|
|
|
float _chargeVoltage;
|
|
float _chargeCurrentLimitation;
|
|
float _dischargeCurrentLimitation;
|
|
uint16_t _stateOfHealth;
|
|
// total current into (positive) or from (negative)
|
|
// the battery, i.e., the charging current
|
|
float _current;
|
|
float _temperature;
|
|
|
|
bool _alarmOverCurrentDischarge;
|
|
bool _alarmOverCurrentCharge;
|
|
bool _alarmUnderTemperature;
|
|
bool _alarmOverTemperature;
|
|
bool _alarmUnderVoltage;
|
|
bool _alarmOverVoltage;
|
|
bool _alarmBmsInternal;
|
|
|
|
bool _warningHighCurrentDischarge;
|
|
bool _warningHighCurrentCharge;
|
|
bool _warningLowTemperature;
|
|
bool _warningHighTemperature;
|
|
bool _warningLowVoltage;
|
|
bool _warningHighVoltage;
|
|
bool _warningBmsInternal;
|
|
|
|
bool _chargeEnabled;
|
|
bool _dischargeEnabled;
|
|
bool _chargeImmediately;
|
|
};
|
|
|
|
class JkBmsBatteryStats : public BatteryStats {
|
|
public:
|
|
void getLiveViewData(JsonVariant& root) const final {
|
|
getJsonData(root, false);
|
|
}
|
|
|
|
void getInfoViewData(JsonVariant& root) const {
|
|
getJsonData(root, true);
|
|
}
|
|
|
|
void mqttPublish() const final;
|
|
|
|
uint32_t getMqttFullPublishIntervalMs() const final { return 60 * 1000; }
|
|
|
|
void updateFrom(JkBms::DataPointContainer const& dp);
|
|
|
|
private:
|
|
void getJsonData(JsonVariant& root, bool verbose) const;
|
|
|
|
JkBms::DataPointContainer _dataPoints;
|
|
mutable uint32_t _lastMqttPublish = 0;
|
|
mutable uint32_t _lastFullMqttPublish = 0;
|
|
|
|
uint16_t _cellMinMilliVolt = 0;
|
|
uint16_t _cellAvgMilliVolt = 0;
|
|
uint16_t _cellMaxMilliVolt = 0;
|
|
uint32_t _cellVoltageTimestamp = 0;
|
|
};
|
|
|
|
class VictronSmartShuntStats : public BatteryStats {
|
|
public:
|
|
void getLiveViewData(JsonVariant& root) const final;
|
|
void mqttPublish() const final;
|
|
|
|
void updateFrom(VeDirectShuntController::veShuntStruct const& shuntData);
|
|
|
|
private:
|
|
float _current;
|
|
float _temperature;
|
|
bool _tempPresent;
|
|
uint8_t _chargeCycles;
|
|
uint32_t _timeToGo;
|
|
float _chargedEnergy;
|
|
float _dischargedEnergy;
|
|
String _modelName;
|
|
|
|
bool _alarmLowVoltage;
|
|
bool _alarmHighVoltage;
|
|
bool _alarmLowSOC;
|
|
bool _alarmLowTemperature;
|
|
bool _alarmHighTemperature;
|
|
};
|
|
|
|
class MqttBatteryStats : public BatteryStats {
|
|
friend class MqttBattery;
|
|
|
|
public:
|
|
// since the source of information was MQTT in the first place,
|
|
// we do NOT publish the same data under a different topic.
|
|
void mqttPublish() const final { }
|
|
|
|
// if the voltage is subscribed to at all, it alone does not warrant a
|
|
// card in the live view, since the SoC is already displayed at the top
|
|
void getLiveViewData(JsonVariant& root) const final { }
|
|
};
|