// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #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; 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; } // returns true if the battery reached a critically low voltage/SoC, // such that it is in need of charging to prevent degredation. virtual bool needsCharging() const { return false; } 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; bool needsCharging() const final { return _chargeImmediately; } 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 { } };