diff --git a/include/Battery.h b/include/Battery.h deleted file mode 100644 index 1265a8bb2..000000000 --- a/include/Battery.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#pragma once - -#include -#include -#include - -#include "BatteryStats.h" - -class BatteryProvider { -public: - // returns true if the provider is ready for use, false otherwise - virtual bool init(bool verboseLogging) = 0; - virtual void deinit() = 0; - virtual void loop() = 0; - virtual std::shared_ptr getStats() const = 0; -}; - -class BatteryClass { -public: - void init(Scheduler&); - void updateSettings(); - - float getDischargeCurrentLimit(); - - std::shared_ptr getStats() const; - -private: - void loop(); - - Task _loopTask; - mutable std::mutex _mutex; - std::unique_ptr _upProvider = nullptr; -}; - -extern BatteryClass Battery; diff --git a/include/BatteryStats.h b/include/BatteryStats.h deleted file mode 100644 index cfce8ea88..000000000 --- a/include/BatteryStats.h +++ /dev/null @@ -1,354 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#pragma once - -#include - -#include "AsyncJson.h" -#include "Arduino.h" -#include "JkBmsDataPoints.h" -#include "JbdBmsDataPoints.h" -#include "VeDirectShuntController.h" -#include - -// mandatory interface for all kinds of batteries -class BatteryStats { - public: - String const& getManufacturer() const { return _manufacturer; } - - // the last time *any* data was updated - uint32_t getAgeSeconds() const { return (millis() - _lastUpdate) / 1000; } - bool updateAvailable(uint32_t since) const; - - float getSoC() const { return _soc; } - uint32_t getSoCAgeSeconds() const { return (millis() - _lastUpdateSoC) / 1000; } - uint8_t getSoCPrecision() const { return _socPrecision; } - - float getVoltage() const { return _voltage; } - uint32_t getVoltageAgeSeconds() const { return (millis() - _lastUpdateVoltage) / 1000; } - - float getChargeCurrent() const { return _current; }; - uint8_t getChargeCurrentPrecision() const { return _currentPrecision; } - - float getDischargeCurrentLimit() const { return _dischargeCurrentLimit; }; - uint32_t getDischargeCurrentLimitAgeSeconds() const { return (millis() - _lastUpdateDischargeCurrentLimit) / 1000; } - - // convert stats to JSON for web application live view - virtual void getLiveViewData(JsonVariant& root) const; - - void mqttLoop(); - - // the interval at which all battery data 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; } - bool isCurrentValid() const { return _lastUpdateCurrent > 0; } - bool isDischargeCurrentLimitValid() const { return _lastUpdateDischargeCurrentLimit > 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 getImmediateChargingRequest() const { return false; }; - - virtual float getChargeCurrentLimitation() const { return FLT_MAX; }; - - virtual bool supportsAlarmsAndWarnings() const { return true; }; - - protected: - virtual void mqttPublish() const; - - void setSoC(float soc, uint8_t precision, uint32_t timestamp) { - _soc = soc; - _socPrecision = precision; - _lastUpdateSoC = _lastUpdate = timestamp; - } - - void setVoltage(float voltage, uint32_t timestamp) { - _voltage = voltage; - _lastUpdateVoltage = _lastUpdate = timestamp; - } - - void setCurrent(float current, uint8_t precision, uint32_t timestamp) { - _current = current; - _currentPrecision = precision; - _lastUpdateCurrent = _lastUpdate = timestamp; - } - - void setDischargeCurrentLimit(float dischargeCurrentLimit, uint32_t timestamp) { - _dischargeCurrentLimit = dischargeCurrentLimit; - _lastUpdateDischargeCurrentLimit = _lastUpdate = timestamp; - } - - void setManufacturer(const String& m); - - String _hwversion = ""; - String _fwversion = ""; - String _serial = ""; - uint32_t _lastUpdate = 0; - - private: - String _manufacturer = "unknown"; - 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; - - // total current into (positive) or from (negative) - // the battery, i.e., the charging current - float _current = 0; - uint8_t _currentPrecision = 0; // decimal places - uint32_t _lastUpdateCurrent = 0; - - float _dischargeCurrentLimit = 0; - uint32_t _lastUpdateDischargeCurrentLimit = 0; -}; - -class PylontechBatteryStats : public BatteryStats { - friend class PylontechCanReceiver; - - public: - void getLiveViewData(JsonVariant& root) const final; - void mqttPublish() const final; - bool getImmediateChargingRequest() const { return _chargeImmediately; } ; - float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ; - - private: - void setLastUpdate(uint32_t ts) { _lastUpdate = ts; } - - float _chargeVoltage; - float _chargeCurrentLimitation; - float _dischargeVoltageLimitation; - uint16_t _stateOfHealth; - 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; - - uint8_t _moduleCount; -}; - -class SBSBatteryStats : public BatteryStats { - friend class SBSCanReceiver; - - public: - void getLiveViewData(JsonVariant& root) const final; - void mqttPublish() const final; - float getChargeCurrent() const { return _current; } ; - float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ; - - private: - void setLastUpdate(uint32_t ts) { _lastUpdate = ts; } - - float _chargeVoltage; - float _chargeCurrentLimitation; - uint16_t _stateOfHealth; - float _current; - float _temperature; - - bool _alarmUnderTemperature; - bool _alarmOverTemperature; - bool _alarmUnderVoltage; - bool _alarmOverVoltage; - bool _alarmBmsInternal; - - bool _warningHighCurrentDischarge; - bool _warningHighCurrentCharge; - - bool _chargeEnabled; - bool _dischargeEnabled; -}; - -class PytesBatteryStats : public BatteryStats { - friend class PytesCanReceiver; - - public: - void getLiveViewData(JsonVariant& root) const final; - void mqttPublish() const final; - bool getImmediateChargingRequest() const { return _chargeImmediately; }; - float getChargeCurrentLimitation() const { return _chargeCurrentLimit; }; - - private: - void setLastUpdate(uint32_t ts) { _lastUpdate = ts; } - void updateSerial() { - if (!_serialPart1.isEmpty() && !_serialPart2.isEmpty()) { - _serial = _serialPart1 + _serialPart2; - } - } - - String _serialPart1 = ""; - String _serialPart2 = ""; - - float _chargeVoltageLimit; - float _chargeCurrentLimit; - float _dischargeVoltageLimit; - - uint16_t _stateOfHealth; - int _chargeCycles = -1; - int _balance = -1; - - float _temperature; - - uint16_t _cellMinMilliVolt; - uint16_t _cellMaxMilliVolt; - float _cellMinTemperature; - float _cellMaxTemperature; - - String _cellMinVoltageName; - String _cellMaxVoltageName; - String _cellMinTemperatureName; - String _cellMaxTemperatureName; - - uint8_t _moduleCountOnline; - uint8_t _moduleCountOffline; - - uint8_t _moduleCountBlockingCharge; - uint8_t _moduleCountBlockingDischarge; - - float _totalCapacity; - float _availableCapacity; - uint8_t _capacityPrecision = 0; // decimal places - - float _chargedEnergy = -1; - float _dischargedEnergy = -1; - - bool _alarmUnderVoltage; - bool _alarmOverVoltage; - bool _alarmOverCurrentCharge; - bool _alarmOverCurrentDischarge; - bool _alarmUnderTemperature; - bool _alarmOverTemperature; - bool _alarmUnderTemperatureCharge; - bool _alarmOverTemperatureCharge; - bool _alarmInternalFailure; - bool _alarmCellImbalance; - - bool _warningLowVoltage; - bool _warningHighVoltage; - bool _warningHighChargeCurrent; - bool _warningHighDischargeCurrent; - bool _warningLowTemperature; - bool _warningHighTemperature; - bool _warningLowTemperatureCharge; - bool _warningHighTemperatureCharge; - bool _warningInternalFailure; - bool _warningCellImbalance; - - 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 JbdBmsBatteryStats : 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(JbdBms::DataPointContainer const& dp); - - private: - void getJsonData(JsonVariant& root, bool verbose) const; - - JbdBms::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::data_t const& shuntData); - - private: - float _temperature; - bool _tempPresent; - uint8_t _chargeCycles; - uint32_t _timeToGo; - float _chargedEnergy; - float _dischargedEnergy; - int32_t _instantaneousPower; - float _midpointVoltage; - float _midpointDeviation; - float _consumedAmpHours; - int32_t _lastFullCharge; - - 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 { } - - void getLiveViewData(JsonVariant& root) const final; - - bool supportsAlarmsAndWarnings() const final { return false; } -}; diff --git a/include/JbdBmsController.h b/include/JbdBmsController.h deleted file mode 100644 index 98764771d..000000000 --- a/include/JbdBmsController.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "Battery.h" -#include "JbdBmsDataPoints.h" -#include "JbdBmsSerialMessage.h" -#include "JbdBmsController.h" - -namespace JbdBms { - -class Controller : public BatteryProvider { - public: - Controller() = default; - - bool init(bool verboseLogging) final; - void deinit() final; - void loop() final; - std::shared_ptr getStats() const final { return _stats; } - - private: - static char constexpr _serialPortOwner[] = "JBD BMS"; - -#ifdef JBDBMS_DUMMY_SERIAL - std::unique_ptr _upSerial; -#else - std::unique_ptr _upSerial; -#endif - - enum class Status : unsigned { - Initializing, - Timeout, - WaitingForPollInterval, - HwSerialNotAvailableForWrite, - BusyReading, - RequestSent, - FrameCompleted - }; - - frozen::string const& getStatusText(Status status); - void announceStatus(Status status); - void sendRequest(uint8_t pollInterval); - void rxData(uint8_t inbyte); - void reset(); - void frameComplete(); - void processDataPoints(DataPointContainer const& dataPoints); - - enum class Interface : unsigned { - Invalid, - Uart, - Transceiver - }; - - Interface getInterface() const; - - enum class ReadState : unsigned { - Idle, - WaitingForFrameStart, - FrameStartReceived, // 1 Byte: 0xDD - StateReceived, - CommandCodeReceived, - ReadingDataContent, - DataContentReceived, - ReadingCheckSum, - CheckSumReceived, - }; - - ReadState _readState; - void setReadState(ReadState state) { - _readState = state; - } - - bool _verboseLogging = true; - int8_t _rxEnablePin = -1; - int8_t _txEnablePin = -1; - Status _lastStatus = Status::Initializing; - uint32_t _lastStatusPrinted = 0; - uint32_t _lastRequest = 0; - uint8_t _dataLength = 0; - JbdBms::SerialResponse::tData _buffer = {}; - std::shared_ptr _stats = - std::make_shared(); -}; - -} /* namespace JbdBms */ diff --git a/include/JkBmsController.h b/include/JkBmsController.h deleted file mode 100644 index b2c71c454..000000000 --- a/include/JkBmsController.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "Battery.h" -#include "JkBmsDataPoints.h" -#include "JkBmsSerialMessage.h" -#include "JkBmsDummy.h" - -//#define JKBMS_DUMMY_SERIAL - -namespace JkBms { - -class Controller : public BatteryProvider { - public: - Controller() = default; - - bool init(bool verboseLogging) final; - void deinit() final; - void loop() final; - std::shared_ptr getStats() const final { return _stats; } - - private: - static char constexpr _serialPortOwner[] = "JK BMS"; - -#ifdef JKBMS_DUMMY_SERIAL - std::unique_ptr _upSerial; -#else - std::unique_ptr _upSerial; -#endif - - enum class Status : unsigned { - Initializing, - Timeout, - WaitingForPollInterval, - HwSerialNotAvailableForWrite, - BusyReading, - RequestSent, - FrameCompleted - }; - - frozen::string const& getStatusText(Status status); - void announceStatus(Status status); - void sendRequest(uint8_t pollInterval); - void rxData(uint8_t inbyte); - void reset(); - void frameComplete(); - void processDataPoints(DataPointContainer const& dataPoints); - - enum class Interface : unsigned { - Invalid, - Uart, - Transceiver - }; - - Interface getInterface() const; - - enum class ReadState : unsigned { - Idle, - WaitingForFrameStart, - FrameStartReceived, - StartMarkerReceived, - FrameLengthMsbReceived, - ReadingFrame - }; - ReadState _readState; - void setReadState(ReadState state) { - _readState = state; - } - - bool _verboseLogging = true; - int8_t _rxEnablePin = -1; - int8_t _txEnablePin = -1; - Status _lastStatus = Status::Initializing; - uint32_t _lastStatusPrinted = 0; - uint32_t _lastRequest = 0; - uint16_t _frameLength = 0; - uint8_t _protocolVersion = -1; - SerialResponse::tData _buffer = {}; - std::shared_ptr _stats = - std::make_shared(); -}; - -} /* namespace JkBms */ diff --git a/include/MqttHandleBatteryHass.h b/include/MqttHandleBatteryHass.h deleted file mode 100644 index f328a6f88..000000000 --- a/include/MqttHandleBatteryHass.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#pragma once - -#include -#include - -class MqttHandleBatteryHassClass { -public: - void init(Scheduler& scheduler); - void forceUpdate() { _doPublish = true; } - -private: - void loop(); - void publish(const String& subtopic, const String& payload); - void publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off); - void publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass = NULL, const char* stateClass = NULL, const char* unitOfMeasurement = NULL); - void createDeviceInfo(JsonObject& object); - - Task _loopTask; - - bool _doPublish = true; - String serial = "0001"; // pseudo-serial, can be replaced in future with real serialnumber -}; - -extern MqttHandleBatteryHassClass MqttHandleBatteryHass; diff --git a/include/PylontechCanReceiver.h b/include/PylontechCanReceiver.h deleted file mode 100644 index 24bae8c06..000000000 --- a/include/PylontechCanReceiver.h +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#pragma once - -#include "Configuration.h" -#include "Battery.h" -#include "BatteryCanReceiver.h" -#include -#include - -class PylontechCanReceiver : public BatteryCanReceiver { -public: - bool init(bool verboseLogging) final; - void onMessage(twai_message_t rx_message) final; - - std::shared_ptr getStats() const final { return _stats; } - -private: - void dummyData(); - - std::shared_ptr _stats = - std::make_shared(); -}; diff --git a/include/PytesCanReceiver.h b/include/PytesCanReceiver.h deleted file mode 100644 index aaeb9d66d..000000000 --- a/include/PytesCanReceiver.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#pragma once - -#include "Configuration.h" -#include "Battery.h" -#include "BatteryCanReceiver.h" -#include - -class PytesCanReceiver : public BatteryCanReceiver { -public: - bool init(bool verboseLogging) final; - void onMessage(twai_message_t rx_message) final; - - std::shared_ptr getStats() const final { return _stats; } - -private: - std::shared_ptr _stats = - std::make_shared(); -}; diff --git a/include/SBSCanReceiver.h b/include/SBSCanReceiver.h deleted file mode 100644 index 0cec94196..000000000 --- a/include/SBSCanReceiver.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#pragma once - -#include "BatteryCanReceiver.h" -#include -#include - -class SBSCanReceiver : public BatteryCanReceiver { -public: - bool init(bool verboseLogging) final; - void onMessage(twai_message_t rx_message) final; - - std::shared_ptr getStats() const final { return _stats; } - -private: - void dummyData(); - std::shared_ptr _stats = - std::make_shared(); -}; diff --git a/include/VictronSmartShunt.h b/include/VictronSmartShunt.h deleted file mode 100644 index 10bf1c523..000000000 --- a/include/VictronSmartShunt.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#pragma once - -#include "Battery.h" - -class VictronSmartShunt : public BatteryProvider { -public: - bool init(bool verboseLogging) final; - void deinit() final; - void loop() final; - std::shared_ptr getStats() const final { return _stats; } - -private: - static char constexpr _serialPortOwner[] = "SmartShunt"; - - uint32_t _lastUpdate = 0; - std::shared_ptr _stats = - std::make_shared(); -}; diff --git a/include/BatteryCanReceiver.h b/include/battery/CanReceiver.h similarity index 83% rename from include/BatteryCanReceiver.h rename to include/battery/CanReceiver.h index 225cceed8..fc82fdc88 100644 --- a/include/BatteryCanReceiver.h +++ b/include/battery/CanReceiver.h @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include "Battery.h" +#include #include -#include +#include -class BatteryCanReceiver : public BatteryProvider { +namespace BatteryNs { + +class CanReceiver : public Provider { public: bool init(bool verboseLogging, char const* providerName); void deinit() final; @@ -27,3 +29,5 @@ class BatteryCanReceiver : public BatteryProvider { private: char const* _providerName = "Battery CAN"; }; + +} // namespace BatteryNs diff --git a/include/battery/Controller.h b/include/battery/Controller.h new file mode 100644 index 000000000..6d53e6486 --- /dev/null +++ b/include/battery/Controller.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include +#include +#include + +namespace BatteryNs { + +class Controller { +public: + void init(Scheduler&); + void updateSettings(); + + float getDischargeCurrentLimit(); + + std::shared_ptr getStats() const; + +private: + void loop(); + + Task _loopTask; + mutable std::mutex _mutex; + std::unique_ptr _upProvider = nullptr; + bool _publishSensors = false; +}; + +} // namespace BatteryNs + +extern BatteryNs::Controller Battery; diff --git a/include/battery/HassIntegration.h b/include/battery/HassIntegration.h new file mode 100644 index 000000000..0c681d6a5 --- /dev/null +++ b/include/battery/HassIntegration.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include + +namespace BatteryNs { + +class HassIntegration { +public: + virtual void publishSensors() const; + +protected: + void publish(const String& subtopic, const String& payload) const; + void publishBinarySensor(const char* caption, + const char* icon, const char* subTopic, + const char* payload_on, const char* payload_off) const; + void publishSensor(const char* caption, const char* icon, + const char* subTopic, const char* deviceClass = nullptr, + const char* stateClass = nullptr, + const char* unitOfMeasurement = nullptr) const; + void createDeviceInfo(JsonObject& object) const; + + String _serial = "0001"; // pseudo-serial, can be replaced in future with real serialnumber +}; + +} // namespace BatteryNs diff --git a/include/battery/Provider.h b/include/battery/Provider.h new file mode 100644 index 000000000..f7cca634b --- /dev/null +++ b/include/battery/Provider.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs { + +class Stats; +class HassIntegration; + +class Provider { +public: + // returns true if the provider is ready for use, false otherwise + virtual bool init(bool verboseLogging) = 0; + virtual void deinit() = 0; + virtual void loop() = 0; + virtual std::shared_ptr getStats() const = 0; + virtual HassIntegration const& getHassIntegration() const = 0; +}; + +} // namespace BatteryNs diff --git a/include/battery/Stats.h b/include/battery/Stats.h new file mode 100644 index 000000000..aae869511 --- /dev/null +++ b/include/battery/Stats.h @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include +#include +#include + +namespace BatteryNs { + +// mandatory interface for all kinds of batteries +class Stats { +public: + String const& getManufacturer() const { return _manufacturer; } + + // the last time *any* data was updated + uint32_t getAgeSeconds() const { return (millis() - _lastUpdate) / 1000; } + bool updateAvailable(uint32_t since) const; + + float getSoC() const { return _soc; } + uint32_t getSoCAgeSeconds() const { return (millis() - _lastUpdateSoC) / 1000; } + uint8_t getSoCPrecision() const { return _socPrecision; } + + float getVoltage() const { return _voltage; } + uint32_t getVoltageAgeSeconds() const { return (millis() - _lastUpdateVoltage) / 1000; } + + float getChargeCurrent() const { return _current; }; + uint8_t getChargeCurrentPrecision() const { return _currentPrecision; } + + float getDischargeCurrentLimit() const { return _dischargeCurrentLimit; }; + uint32_t getDischargeCurrentLimitAgeSeconds() const { return (millis() - _lastUpdateDischargeCurrentLimit) / 1000; } + + // convert stats to JSON for web application live view + virtual void getLiveViewData(JsonVariant& root) const; + + void mqttLoop(); + + // the interval at which all battery data 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; } + bool isCurrentValid() const { return _lastUpdateCurrent > 0; } + bool isDischargeCurrentLimitValid() const { return _lastUpdateDischargeCurrentLimit > 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 getImmediateChargingRequest() const { return false; }; + + virtual float getChargeCurrentLimitation() const { return FLT_MAX; }; + + virtual bool supportsAlarmsAndWarnings() const { return true; }; + +protected: + virtual void mqttPublish() const; + + void setSoC(float soc, uint8_t precision, uint32_t timestamp) { + _soc = soc; + _socPrecision = precision; + _lastUpdateSoC = _lastUpdate = timestamp; + } + + void setVoltage(float voltage, uint32_t timestamp) { + _voltage = voltage; + _lastUpdateVoltage = _lastUpdate = timestamp; + } + + void setCurrent(float current, uint8_t precision, uint32_t timestamp) { + _current = current; + _currentPrecision = precision; + _lastUpdateCurrent = _lastUpdate = timestamp; + } + + void setDischargeCurrentLimit(float dischargeCurrentLimit, uint32_t timestamp) { + _dischargeCurrentLimit = dischargeCurrentLimit; + _lastUpdateDischargeCurrentLimit = _lastUpdate = timestamp; + } + + void setManufacturer(const String& m); + + template + static void addLiveViewInSection(JsonVariant& root, + std::string const& section, std::string const& name, + T&& value, std::string const& unit, uint8_t precision) + { + auto jsonValue = root["values"][section][name]; + jsonValue["v"] = value; + jsonValue["u"] = unit; + jsonValue["d"] = precision; + } + + template + static void addLiveViewValue(JsonVariant& root, std::string const& name, + T&& value, std::string const& unit, uint8_t precision) + { + addLiveViewInSection(root, "status", name, value, unit, precision); + } + + static void addLiveViewTextInSection(JsonVariant& root, + std::string const& section, std::string const& name, + std::string const& text, bool translate = true) + { + auto jsonValue = root["values"][section][name]; + jsonValue["value"] = text; + jsonValue["translate"] = translate; + } + + static void addLiveViewTextValue(JsonVariant& root, std::string const& name, + std::string const& text) + { + addLiveViewTextInSection(root, "status", name, text); + } + + static void addLiveViewWarning(JsonVariant& root, std::string const& name, + bool warning) + { + if (!warning) { return; } + root["issues"][name] = 1; + } + + static void addLiveViewAlarm(JsonVariant& root, std::string const& name, + bool alarm) + { + if (!alarm) { return; } + root["issues"][name] = 2; + } + + String _hwversion = ""; + String _fwversion = ""; + String _serial = ""; + uint32_t _lastUpdate = 0; + +private: + String _manufacturer = "unknown"; + 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; + + // total current into (positive) or from (negative) + // the battery, i.e., the charging current + float _current = 0; + uint8_t _currentPrecision = 0; // decimal places + uint32_t _lastUpdateCurrent = 0; + + float _dischargeCurrentLimit = 0; + uint32_t _lastUpdateDischargeCurrentLimit = 0; +}; + +} // namespace BatteryNs diff --git a/include/JbdBmsDataPoints.h b/include/battery/jbdbms/DataPoints.h similarity index 91% rename from include/JbdBmsDataPoints.h rename to include/battery/jbdbms/DataPoints.h index 009558af2..f7b620ffc 100644 --- a/include/JbdBmsDataPoints.h +++ b/include/battery/jbdbms/DataPoints.h @@ -4,10 +4,9 @@ #include #include #include +#include -#include "DataPoints.h" - -namespace JbdBms { +namespace BatteryNs::JbdBms { #define JBD_PROTECTION_STATUS(fnc) \ fnc(CellOverVoltage, (1<<0)) \ @@ -61,7 +60,7 @@ enum class DataPointLabel : uint8_t { ActualBatteryCapacityAmpHours }; -using tCells = tCellVoltages; +using tCells = ::tCellVoltages; template struct DataPointLabelTraits; @@ -106,13 +105,13 @@ LABEL_TRAIT(BmsHardwareVersion, std::string, ""); LABEL_TRAIT(ActualBatteryCapacityAmpHours, uint32_t, "Ah"); #undef LABEL_TRAIT -} /* namespace JbdBms */ +} // namespace BatteryNs::JbdBms using JbdBmsDataPoint = DataPoint; + int16_t, int32_t, std::string, BatteryNs::JbdBms::tCells>; -template class DataPointContainer; +template class DataPointContainer; -namespace JbdBms { +namespace BatteryNs::JbdBms { using DataPointContainer = DataPointContainer; -} /* namespace JbdBms */ +} // namespace BatteryNs::JbdBms diff --git a/include/battery/jbdbms/HassIntegration.h b/include/battery/jbdbms/HassIntegration.h new file mode 100644 index 000000000..9157c5f71 --- /dev/null +++ b/include/battery/jbdbms/HassIntegration.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::JbdBms { + +class HassIntegration : public ::BatteryNs::HassIntegration { +public: + void publishSensors() const final; +}; + +} // namespace BatteryNs::JbdBms diff --git a/include/battery/jbdbms/Provider.h b/include/battery/jbdbms/Provider.h new file mode 100644 index 000000000..8a8a4cd77 --- /dev/null +++ b/include/battery/jbdbms/Provider.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace BatteryNs::JbdBms { + +class Provider : public ::BatteryNs::Provider { +public: + Provider() = default; + + bool init(bool verboseLogging) final; + void deinit() final; + void loop() final; + std::shared_ptr<::BatteryNs::Stats> getStats() const final { return _stats; } + ::BatteryNs::HassIntegration const& getHassIntegration() const final { return _hassIntegration; } + +private: + static char constexpr _serialPortOwner[] = "JBD BMS"; + +#ifdef JBDBMS_DUMMY_SERIAL + std::unique_ptr _upSerial; +#else + std::unique_ptr _upSerial; +#endif + + enum class Status : unsigned { + Initializing, + Timeout, + WaitingForPollInterval, + HwSerialNotAvailableForWrite, + BusyReading, + RequestSent, + FrameCompleted + }; + + frozen::string const& getStatusText(Status status); + void announceStatus(Status status); + void sendRequest(uint8_t pollInterval); + void rxData(uint8_t inbyte); + void reset(); + void frameComplete(); + void processDataPoints(DataPointContainer const& dataPoints); + + enum class Interface : unsigned { + Invalid, + Uart, + Transceiver + }; + + Interface getInterface() const; + + enum class ReadState : unsigned { + Idle, + WaitingForFrameStart, + FrameStartReceived, // 1 Byte: 0xDD + StateReceived, + CommandCodeReceived, + ReadingDataContent, + DataContentReceived, + ReadingCheckSum, + CheckSumReceived, + }; + + ReadState _readState; + void setReadState(ReadState state) { + _readState = state; + } + + bool _verboseLogging = true; + int8_t _rxEnablePin = -1; + int8_t _txEnablePin = -1; + Status _lastStatus = Status::Initializing; + uint32_t _lastStatusPrinted = 0; + uint32_t _lastRequest = 0; + uint8_t _dataLength = 0; + JbdBms::SerialResponse::tData _buffer = {}; + std::shared_ptr _stats = + std::make_shared(); + HassIntegration _hassIntegration; +}; + +} // namespace BatteryNs::JbdBms diff --git a/include/JbdBmsSerialMessage.h b/include/battery/jbdbms/SerialMessage.h similarity index 95% rename from include/JbdBmsSerialMessage.h rename to include/battery/jbdbms/SerialMessage.h index 6376ca317..f7b36483a 100644 --- a/include/JbdBmsSerialMessage.h +++ b/include/battery/jbdbms/SerialMessage.h @@ -4,9 +4,9 @@ #include #include -#include "JbdBmsDataPoints.h" +#include -namespace JbdBms { +namespace BatteryNs::JbdBms { // Only valid for receiving messages class SerialMessage { @@ -52,7 +52,7 @@ class SerialMessage { void updateChecksum(); tData _raw; - JbdBms::DataPointContainer _dp; + DataPointContainer _dp; }; class SerialResponse : public SerialMessage { @@ -92,5 +92,4 @@ class SerialCommand : public SerialMessage { static Command _lastCmd; }; - -} /* namespace JbdBms */ +} // namespace BatteryNs::JbdBms diff --git a/include/battery/jbdbms/Stats.h b/include/battery/jbdbms/Stats.h new file mode 100644 index 000000000..fd9c21720 --- /dev/null +++ b/include/battery/jbdbms/Stats.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include + +namespace BatteryNs::JbdBms { + +class Stats : public ::BatteryNs::Stats { +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(DataPointContainer const& dp); + +private: + void getJsonData(JsonVariant& root, bool verbose) const; + + 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; +}; + +} // namespace BatteryNs::JbdBms diff --git a/include/JkBmsDataPoints.h b/include/battery/jkbms/DataPoints.h similarity index 96% rename from include/JkBmsDataPoints.h rename to include/battery/jkbms/DataPoints.h index cd9923b5d..444aae92b 100644 --- a/include/JkBmsDataPoints.h +++ b/include/battery/jkbms/DataPoints.h @@ -4,10 +4,9 @@ #include #include #include +#include -#include "DataPoints.h" - -namespace JkBms { +namespace BatteryNs::JkBms { #define ALARM_BITS(fnc) \ fnc(LowCapacity, (1<<0)) \ @@ -204,13 +203,13 @@ LABEL_TRAIT(ProductId, std::string, ""); LABEL_TRAIT(ProtocolVersion, uint8_t, ""); #undef LABEL_TRAIT -} /* namespace JkBms */ +} // namespace BatteryNs::JkBms using JkBmsDataPoint = DataPoint; + int16_t, int32_t, std::string, BatteryNs::JkBms::tCells>; -template class DataPointContainer; +template class DataPointContainer; -namespace JkBms { +namespace BatteryNs::JkBms { using DataPointContainer = DataPointContainer; -} /* namespace JkBms */ +} // namespace BatteryNs::JkBms diff --git a/include/JkBmsDummy.h b/include/battery/jkbms/Dummy.h similarity index 99% rename from include/JkBmsDummy.h rename to include/battery/jkbms/Dummy.h index 1524023ae..b3c2cd592 100644 --- a/include/JkBmsDummy.h +++ b/include/battery/jkbms/Dummy.h @@ -2,9 +2,9 @@ #include #include -#include "MessageOutput.h" +#include -namespace JkBms { +namespace BatteryNs::JkBms { class DummySerial { public: @@ -193,4 +193,4 @@ class DummySerial { size_t _byte_idx = 0; }; -} /* namespace JkBms */ +} // namespace BatteryNs::JkBms diff --git a/include/battery/jkbms/HassIntegration.h b/include/battery/jkbms/HassIntegration.h new file mode 100644 index 000000000..fdea4bc5c --- /dev/null +++ b/include/battery/jkbms/HassIntegration.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::JkBms { + +class HassIntegration : public ::BatteryNs::HassIntegration { +public: + void publishSensors() const final; +}; + +} // namespace BatteryNs::JkBms diff --git a/include/battery/jkbms/Provider.h b/include/battery/jkbms/Provider.h new file mode 100644 index 000000000..762f9f9aa --- /dev/null +++ b/include/battery/jkbms/Provider.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +//#define JKBMS_DUMMY_SERIAL + +namespace BatteryNs::JkBms { + +class Provider : public ::BatteryNs::Provider { +public: + Provider() = default; + + bool init(bool verboseLogging) final; + void deinit() final; + void loop() final; + std::shared_ptr<::BatteryNs::Stats> getStats() const final { return _stats; } + ::BatteryNs::HassIntegration const& getHassIntegration() const final { return _hassIntegration; } + +private: + static char constexpr _serialPortOwner[] = "JK BMS"; + +#ifdef JKBMS_DUMMY_SERIAL + std::unique_ptr _upSerial; +#else + std::unique_ptr _upSerial; +#endif + + enum class Status : unsigned { + Initializing, + Timeout, + WaitingForPollInterval, + HwSerialNotAvailableForWrite, + BusyReading, + RequestSent, + FrameCompleted + }; + + frozen::string const& getStatusText(Status status); + void announceStatus(Status status); + void sendRequest(uint8_t pollInterval); + void rxData(uint8_t inbyte); + void reset(); + void frameComplete(); + void processDataPoints(DataPointContainer const& dataPoints); + + enum class Interface : unsigned { + Invalid, + Uart, + Transceiver + }; + + Interface getInterface() const; + + enum class ReadState : unsigned { + Idle, + WaitingForFrameStart, + FrameStartReceived, + StartMarkerReceived, + FrameLengthMsbReceived, + ReadingFrame + }; + ReadState _readState; + void setReadState(ReadState state) { + _readState = state; + } + + bool _verboseLogging = true; + int8_t _rxEnablePin = -1; + int8_t _txEnablePin = -1; + Status _lastStatus = Status::Initializing; + uint32_t _lastStatusPrinted = 0; + uint32_t _lastRequest = 0; + uint16_t _frameLength = 0; + uint8_t _protocolVersion = -1; + SerialResponse::tData _buffer = {}; + std::shared_ptr _stats = + std::make_shared(); + HassIntegration _hassIntegration; +}; + +} // namespace BatteryNs::JkBms diff --git a/include/JkBmsSerialMessage.h b/include/battery/jkbms/SerialMessage.h similarity index 95% rename from include/JkBmsSerialMessage.h rename to include/battery/jkbms/SerialMessage.h index 6f869d70a..e36216eb6 100644 --- a/include/JkBmsSerialMessage.h +++ b/include/battery/jkbms/SerialMessage.h @@ -4,9 +4,9 @@ #include #include -#include "JkBmsDataPoints.h" +#include -namespace JkBms { +namespace BatteryNs::JkBms { class SerialMessage { public: @@ -70,7 +70,7 @@ class SerialMessage { void updateChecksum(); tData _raw; - JkBms::DataPointContainer _dp; + DataPointContainer _dp; static constexpr uint16_t startMarker = 0x4e57; static constexpr uint8_t endMarker = 0x68; @@ -90,4 +90,4 @@ class SerialCommand : public SerialMessage { explicit SerialCommand(Command cmd); }; -} /* namespace JkBms */ +} // namespace BatteryNs::JkBms diff --git a/include/battery/jkbms/Stats.h b/include/battery/jkbms/Stats.h new file mode 100644 index 000000000..9c640b769 --- /dev/null +++ b/include/battery/jkbms/Stats.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include + +namespace BatteryNs::JkBms { + +class Stats : public ::BatteryNs::Stats { +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(DataPointContainer const& dp); + +private: + void getJsonData(JsonVariant& root, bool verbose) const; + + 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; +}; + +} // namespace BatteryNs::JkBms diff --git a/include/battery/mqtt/HassIntegration.h b/include/battery/mqtt/HassIntegration.h new file mode 100644 index 000000000..dce0b1785 --- /dev/null +++ b/include/battery/mqtt/HassIntegration.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::Mqtt { + +class HassIntegration : public ::BatteryNs::HassIntegration { +public: + void publishSensors() const final + { + ::BatteryNs::HassIntegration::publishSensors(); + } +}; + +} // namespace BatteryNs::Mqtt diff --git a/include/MqttBattery.h b/include/battery/mqtt/Provider.h similarity index 65% rename from include/MqttBattery.h rename to include/battery/mqtt/Provider.h index 04d4a6ab4..4779f7f44 100644 --- a/include/MqttBattery.h +++ b/include/battery/mqtt/Provider.h @@ -1,24 +1,30 @@ #pragma once -#include -#include "Battery.h" +#include #include +#include +#include +#include -class MqttBattery : public BatteryProvider { +namespace BatteryNs::Mqtt { + +class Provider : public ::BatteryNs::Provider { public: - MqttBattery() = default; + Provider() = default; bool init(bool verboseLogging) final; void deinit() final; void loop() final { return; } // this class is event-driven - std::shared_ptr getStats() const final { return _stats; } + std::shared_ptr<::BatteryNs::Stats> getStats() const final { return _stats; } + ::BatteryNs::HassIntegration const& getHassIntegration() const final { return _hassIntegration; } private: bool _verboseLogging = false; String _socTopic; String _voltageTopic; String _dischargeCurrentLimitTopic; - std::shared_ptr _stats = std::make_shared(); + std::shared_ptr _stats = std::make_shared(); + HassIntegration _hassIntegration; uint8_t _socPrecision = 0; void onMqttMessageSoC(espMqttClientTypes::MessageProperties const& properties, @@ -31,3 +37,5 @@ class MqttBattery : public BatteryProvider { char const* topic, uint8_t const* payload, size_t len, size_t index, size_t total, char const* jsonPath); }; + +} // namespace BatteryNs::Mqtt diff --git a/include/battery/mqtt/Stats.h b/include/battery/mqtt/Stats.h new file mode 100644 index 000000000..55275b5aa --- /dev/null +++ b/include/battery/mqtt/Stats.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::Mqtt { + +class Stats : public ::BatteryNs::Stats { +friend class Provider; + +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 { } + + void getLiveViewData(JsonVariant& root) const final; + + bool supportsAlarmsAndWarnings() const final { return false; } +}; + +} // namespace BatteryNs::Mqtt diff --git a/include/battery/pylontech/HassIntegration.h b/include/battery/pylontech/HassIntegration.h new file mode 100644 index 000000000..000ebbeb3 --- /dev/null +++ b/include/battery/pylontech/HassIntegration.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::Pylontech { + +class HassIntegration : public ::BatteryNs::HassIntegration { +public: + void publishSensors() const final; +}; + +} // namespace BatteryNs::Pylontech diff --git a/include/battery/pylontech/Provider.h b/include/battery/pylontech/Provider.h new file mode 100644 index 000000000..32844f1e3 --- /dev/null +++ b/include/battery/pylontech/Provider.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include +#include +#include + +namespace BatteryNs::Pylontech { + +class Provider : public ::BatteryNs::CanReceiver { +public: + bool init(bool verboseLogging) final; + void onMessage(twai_message_t rx_message) final; + + std::shared_ptr<::BatteryNs::Stats> getStats() const final { return _stats; } + ::BatteryNs::HassIntegration const& getHassIntegration() const final { return _hassIntegration; } + +private: + void dummyData(); + + std::shared_ptr _stats = + std::make_shared(); + HassIntegration _hassIntegration; +}; + +} // namespace BatteryNs::Pylontech diff --git a/include/battery/pylontech/Stats.h b/include/battery/pylontech/Stats.h new file mode 100644 index 000000000..f3015babe --- /dev/null +++ b/include/battery/pylontech/Stats.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::Pylontech { + +class Stats : public ::BatteryNs::Stats { +friend class Provider; + +public: + void getLiveViewData(JsonVariant& root) const final; + void mqttPublish() const final; + bool getImmediateChargingRequest() const { return _chargeImmediately; } ; + float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ; + +private: + void setLastUpdate(uint32_t ts) { _lastUpdate = ts; } + + float _chargeVoltage; + float _chargeCurrentLimitation; + float _dischargeVoltageLimitation; + uint16_t _stateOfHealth; + 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; + + uint8_t _moduleCount; +}; + +} // namespace BatteryNs::Pylontech diff --git a/include/battery/pytes/HassIntegration.h b/include/battery/pytes/HassIntegration.h new file mode 100644 index 000000000..81e7cef08 --- /dev/null +++ b/include/battery/pytes/HassIntegration.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::Pytes { + +class HassIntegration : public ::BatteryNs::HassIntegration { +public: + void publishSensors() const final; +}; + +} // namespace BatteryNs::Pytes diff --git a/include/battery/pytes/Provider.h b/include/battery/pytes/Provider.h new file mode 100644 index 000000000..59042bf35 --- /dev/null +++ b/include/battery/pytes/Provider.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include +#include +#include + +namespace BatteryNs::Pytes { + +class Provider : public ::BatteryNs::CanReceiver { +public: + bool init(bool verboseLogging) final; + void onMessage(twai_message_t rx_message) final; + + std::shared_ptr<::BatteryNs::Stats> getStats() const final { return _stats; } + ::BatteryNs::HassIntegration const& getHassIntegration() const final { return _hassIntegration; } + +private: + std::shared_ptr _stats = + std::make_shared(); + HassIntegration _hassIntegration; +}; + +} // namespace BatteryNs::Pytes diff --git a/include/battery/pytes/Stats.h b/include/battery/pytes/Stats.h new file mode 100644 index 000000000..8bae900ea --- /dev/null +++ b/include/battery/pytes/Stats.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::Pytes { + +class Stats : public ::BatteryNs::Stats { +friend class Provider; + +public: + void getLiveViewData(JsonVariant& root) const final; + void mqttPublish() const final; + bool getImmediateChargingRequest() const { return _chargeImmediately; }; + float getChargeCurrentLimitation() const { return _chargeCurrentLimit; }; + +private: + void setLastUpdate(uint32_t ts) { _lastUpdate = ts; } + void updateSerial() { + if (!_serialPart1.isEmpty() && !_serialPart2.isEmpty()) { + _serial = _serialPart1 + _serialPart2; + } + } + + String _serialPart1 = ""; + String _serialPart2 = ""; + + float _chargeVoltageLimit; + float _chargeCurrentLimit; + float _dischargeVoltageLimit; + + uint16_t _stateOfHealth; + int _chargeCycles = -1; + int _balance = -1; + + float _temperature; + + uint16_t _cellMinMilliVolt; + uint16_t _cellMaxMilliVolt; + float _cellMinTemperature; + float _cellMaxTemperature; + + String _cellMinVoltageName; + String _cellMaxVoltageName; + String _cellMinTemperatureName; + String _cellMaxTemperatureName; + + uint8_t _moduleCountOnline; + uint8_t _moduleCountOffline; + + uint8_t _moduleCountBlockingCharge; + uint8_t _moduleCountBlockingDischarge; + + float _totalCapacity; + float _availableCapacity; + uint8_t _capacityPrecision = 0; // decimal places + + float _chargedEnergy = -1; + float _dischargedEnergy = -1; + + bool _alarmUnderVoltage; + bool _alarmOverVoltage; + bool _alarmOverCurrentCharge; + bool _alarmOverCurrentDischarge; + bool _alarmUnderTemperature; + bool _alarmOverTemperature; + bool _alarmUnderTemperatureCharge; + bool _alarmOverTemperatureCharge; + bool _alarmInternalFailure; + bool _alarmCellImbalance; + + bool _warningLowVoltage; + bool _warningHighVoltage; + bool _warningHighChargeCurrent; + bool _warningHighDischargeCurrent; + bool _warningLowTemperature; + bool _warningHighTemperature; + bool _warningLowTemperatureCharge; + bool _warningHighTemperatureCharge; + bool _warningInternalFailure; + bool _warningCellImbalance; + + bool _chargeImmediately; +}; + +} // namespace BatteryNs::Pytes diff --git a/include/battery/sbs/HassIntegration.h b/include/battery/sbs/HassIntegration.h new file mode 100644 index 000000000..6afafd8f8 --- /dev/null +++ b/include/battery/sbs/HassIntegration.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::SBS { + +class HassIntegration : public ::BatteryNs::HassIntegration { +public: + void publishSensors() const final; +}; + +} // namespace BatteryNs::SBS diff --git a/include/battery/sbs/Provider.h b/include/battery/sbs/Provider.h new file mode 100644 index 000000000..7839ee69c --- /dev/null +++ b/include/battery/sbs/Provider.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include +#include +#include + +namespace BatteryNs::SBS { + +class Provider : public ::BatteryNs::CanReceiver { +public: + bool init(bool verboseLogging) final; + void onMessage(twai_message_t rx_message) final; + + std::shared_ptr<::BatteryNs::Stats> getStats() const final { return _stats; } + ::BatteryNs::HassIntegration const& getHassIntegration() const final { return _hassIntegration; } + +private: + void dummyData(); + std::shared_ptr _stats = + std::make_shared(); + HassIntegration _hassIntegration; +}; + +} // namespace BatteryNs::SBS diff --git a/include/battery/sbs/Stats.h b/include/battery/sbs/Stats.h new file mode 100644 index 000000000..c82fe6912 --- /dev/null +++ b/include/battery/sbs/Stats.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::SBS { + +class Stats : public ::BatteryNs::Stats { +friend class Provider; + +public: + void getLiveViewData(JsonVariant& root) const final; + void mqttPublish() const final; + float getChargeCurrent() const { return _current; } ; + float getChargeCurrentLimitation() const { return _chargeCurrentLimitation; } ; + +private: + void setLastUpdate(uint32_t ts) { _lastUpdate = ts; } + + float _chargeVoltage; + float _chargeCurrentLimitation; + uint16_t _stateOfHealth; + float _current; + float _temperature; + + bool _alarmUnderTemperature; + bool _alarmOverTemperature; + bool _alarmUnderVoltage; + bool _alarmOverVoltage; + bool _alarmBmsInternal; + + bool _warningHighCurrentDischarge; + bool _warningHighCurrentCharge; + + bool _chargeEnabled; + bool _dischargeEnabled; +}; + +} // namespace BatteryNs::SBS diff --git a/include/battery/victronsmartshunt/HassIntegration.h b/include/battery/victronsmartshunt/HassIntegration.h new file mode 100644 index 000000000..41aa11411 --- /dev/null +++ b/include/battery/victronsmartshunt/HassIntegration.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +namespace BatteryNs::VictronSmartShunt { + +class HassIntegration : public ::BatteryNs::HassIntegration { +public: + void publishSensors() const final; +}; + +} // namespace BatteryNs::VictronSmartShunt diff --git a/include/battery/victronsmartshunt/Provider.h b/include/battery/victronsmartshunt/Provider.h new file mode 100644 index 000000000..5c70c9348 --- /dev/null +++ b/include/battery/victronsmartshunt/Provider.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include +#include + +namespace BatteryNs::VictronSmartShunt { + +class Provider : public ::BatteryNs::Provider { +public: + bool init(bool verboseLogging) final; + void deinit() final; + void loop() final; + std::shared_ptr<::BatteryNs::Stats> getStats() const final { return _stats; } + ::BatteryNs::HassIntegration const& getHassIntegration() const final { return _hassIntegration; } + +private: + static char constexpr _serialPortOwner[] = "SmartShunt"; + + uint32_t _lastUpdate = 0; + std::shared_ptr _stats = + std::make_shared(); + HassIntegration _hassIntegration; +}; + +} // namespace BatteryNs::VictronSmartShunt diff --git a/include/battery/victronsmartshunt/Stats.h b/include/battery/victronsmartshunt/Stats.h new file mode 100644 index 000000000..8b14dee13 --- /dev/null +++ b/include/battery/victronsmartshunt/Stats.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include + +namespace BatteryNs::VictronSmartShunt { + +class Stats : public ::BatteryNs::Stats { +public: + void getLiveViewData(JsonVariant& root) const final; + void mqttPublish() const final; + + void updateFrom(VeDirectShuntController::data_t const& shuntData); + +private: + float _temperature; + bool _tempPresent; + uint8_t _chargeCycles; + uint32_t _timeToGo; + float _chargedEnergy; + float _dischargedEnergy; + int32_t _instantaneousPower; + float _midpointVoltage; + float _midpointDeviation; + float _consumedAmpHours; + int32_t _lastFullCharge; + + bool _alarmLowVoltage; + bool _alarmHighVoltage; + bool _alarmLowSOC; + bool _alarmLowTemperature; + bool _alarmHighTemperature; +}; + +} // namespace BatteryNs::VictronSmartShunt diff --git a/src/BatteryStats.cpp b/src/BatteryStats.cpp deleted file mode 100644 index f4e383b29..000000000 --- a/src/BatteryStats.cpp +++ /dev/null @@ -1,903 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include "BatteryStats.h" -#include "Configuration.h" -#include "MqttSettings.h" -#include "JkBmsDataPoints.h" -#include "JbdBmsDataPoints.h" -#include "MqttSettings.h" - -template -static void addLiveViewInSection(JsonVariant& root, - std::string const& section, std::string const& name, - T&& value, std::string const& unit, uint8_t precision) -{ - auto jsonValue = root["values"][section][name]; - jsonValue["v"] = value; - jsonValue["u"] = unit; - jsonValue["d"] = precision; -} - -template -static void addLiveViewValue(JsonVariant& root, std::string const& name, - T&& value, std::string const& unit, uint8_t precision) -{ - addLiveViewInSection(root, "status", name, value, unit, precision); -} - -static void addLiveViewTextInSection(JsonVariant& root, - std::string const& section, std::string const& name, - std::string const& text, bool translate = true) -{ - auto jsonValue = root["values"][section][name]; - jsonValue["value"] = text; - jsonValue["translate"] = translate; -} - -static void addLiveViewTextValue(JsonVariant& root, std::string const& name, - std::string const& text) -{ - addLiveViewTextInSection(root, "status", name, text); -} - -static void addLiveViewWarning(JsonVariant& root, std::string const& name, - bool warning) -{ - if (!warning) { return; } - root["issues"][name] = 1; -} - -static void addLiveViewAlarm(JsonVariant& root, std::string const& name, - bool alarm) -{ - if (!alarm) { return; } - root["issues"][name] = 2; -} - -void BatteryStats::setManufacturer(const String& m) -{ - String sanitized(m); - for (int i = 0; i < sanitized.length(); i++) { - char c = sanitized[i]; - if (c < 0x20 || c >= 0x80) { - sanitized.remove(i); // Truncate string - break; - } - } - _manufacturer = std::move(sanitized); -} - -bool BatteryStats::updateAvailable(uint32_t since) const -{ - if (_lastUpdate == 0) { return false; } // no data at all processed yet - - auto constexpr halfOfAllMillis = std::numeric_limits::max() / 2; - return (_lastUpdate - since) < halfOfAllMillis; -} - -void BatteryStats::getLiveViewData(JsonVariant& root) const -{ - root["manufacturer"] = _manufacturer; - if (!_serial.isEmpty()) { - root["serial"] = _serial; - } - if (!_fwversion.isEmpty()) { - root["fwversion"] = _fwversion; - } - if (!_hwversion.isEmpty()) { - root["hwversion"] = _hwversion; - } - root["data_age"] = getAgeSeconds(); - - if (isSoCValid()) { - addLiveViewValue(root, "SoC", _soc, "%", _socPrecision); - } - - if (isVoltageValid()) { - addLiveViewValue(root, "voltage", _voltage, "V", 2); - } - - if (isCurrentValid()) { - addLiveViewValue(root, "current", _current, "A", _currentPrecision); - } - - if (isDischargeCurrentLimitValid()) { - addLiveViewValue(root, "dischargeCurrentLimitation", _dischargeCurrentLimit, "A", 1); - } - - root["showIssues"] = supportsAlarmsAndWarnings(); -} - -void MqttBatteryStats::getLiveViewData(JsonVariant& root) const -{ - // as we don't want to repeat the data that is already shown in the live data card - // we only add the live view data here when the discharge current limit can be shown - if (isDischargeCurrentLimitValid()) { - BatteryStats::getLiveViewData(root); - } -} - -void PylontechBatteryStats::getLiveViewData(JsonVariant& root) const -{ - BatteryStats::getLiveViewData(root); - - // values go into the "Status" card of the web application - addLiveViewValue(root, "chargeVoltage", _chargeVoltage, "V", 1); - addLiveViewValue(root, "chargeCurrentLimitation", _chargeCurrentLimitation, "A", 1); - addLiveViewValue(root, "dischargeVoltageLimitation", _dischargeVoltageLimitation, "V", 1); - addLiveViewValue(root, "stateOfHealth", _stateOfHealth, "%", 0); - addLiveViewValue(root, "temperature", _temperature, "°C", 1); - addLiveViewValue(root, "modules", _moduleCount, "", 0); - - addLiveViewTextValue(root, "chargeEnabled", (_chargeEnabled?"yes":"no")); - addLiveViewTextValue(root, "dischargeEnabled", (_dischargeEnabled?"yes":"no")); - addLiveViewTextValue(root, "chargeImmediately", (_chargeImmediately?"yes":"no")); - - // alarms and warnings go into the "Issues" card of the web application - addLiveViewWarning(root, "highCurrentDischarge", _warningHighCurrentDischarge); - addLiveViewAlarm(root, "overCurrentDischarge", _alarmOverCurrentDischarge); - - addLiveViewWarning(root, "highCurrentCharge", _warningHighCurrentCharge); - addLiveViewAlarm(root, "overCurrentCharge", _alarmOverCurrentCharge); - - addLiveViewWarning(root, "lowTemperature", _warningLowTemperature); - addLiveViewAlarm(root, "underTemperature", _alarmUnderTemperature); - - addLiveViewWarning(root, "highTemperature", _warningHighTemperature); - addLiveViewAlarm(root, "overTemperature", _alarmOverTemperature); - - addLiveViewWarning(root, "lowVoltage", _warningLowVoltage); - addLiveViewAlarm(root, "underVoltage", _alarmUnderVoltage); - - addLiveViewWarning(root, "highVoltage", _warningHighVoltage); - addLiveViewAlarm(root, "overVoltage", _alarmOverVoltage); - - addLiveViewWarning(root, "bmsInternal", _warningBmsInternal); - addLiveViewAlarm(root, "bmsInternal", _alarmBmsInternal); -} - -void SBSBatteryStats::getLiveViewData(JsonVariant& root) const -{ - BatteryStats::getLiveViewData(root); - - // values go into the "Status" card of the web application - addLiveViewValue(root, "chargeVoltage", _chargeVoltage, "V", 1); - addLiveViewValue(root, "chargeCurrentLimitation", _chargeCurrentLimitation, "A", 1); - addLiveViewValue(root, "stateOfHealth", _stateOfHealth, "%", 0); - addLiveViewValue(root, "current", _current, "A", 1); - addLiveViewValue(root, "temperature", _temperature, "°C", 1); - addLiveViewTextValue(root, "chargeEnabled", (_chargeEnabled?"yes":"no")); - addLiveViewTextValue(root, "dischargeEnabled", (_dischargeEnabled?"yes":"no")); - - // alarms and warnings go into the "Issues" card of the web application - addLiveViewWarning(root, "highCurrentDischarge", _warningHighCurrentDischarge); - addLiveViewWarning(root, "highCurrentCharge", _warningHighCurrentCharge); - addLiveViewAlarm(root, "underVoltage", _alarmUnderVoltage); - addLiveViewAlarm(root, "overVoltage", _alarmOverVoltage); - addLiveViewAlarm(root, "bmsInternal", _alarmBmsInternal); - addLiveViewAlarm(root, "underTemperature", _alarmUnderTemperature); - addLiveViewAlarm(root, "overTemperature", _alarmOverTemperature); -} - -void PytesBatteryStats::getLiveViewData(JsonVariant& root) const -{ - BatteryStats::getLiveViewData(root); - - // values go into the "Status" card of the web application - addLiveViewValue(root, "chargeVoltage", _chargeVoltageLimit, "V", 1); - addLiveViewValue(root, "chargeCurrentLimitation", _chargeCurrentLimit, "A", 1); - addLiveViewValue(root, "dischargeVoltageLimitation", _dischargeVoltageLimit, "V", 1); - addLiveViewValue(root, "stateOfHealth", _stateOfHealth, "%", 0); - if (_chargeCycles != -1) { - addLiveViewValue(root, "chargeCycles", _chargeCycles, "", 0); - } - addLiveViewValue(root, "temperature", _temperature, "°C", 1); - - addLiveViewValue(root, "capacity", _totalCapacity, "Ah", _capacityPrecision); - addLiveViewValue(root, "availableCapacity", _availableCapacity, "Ah", _capacityPrecision); - - if (_chargedEnergy != -1) { - addLiveViewValue(root, "chargedEnergy", _chargedEnergy, "kWh", 1); - } - - if (_dischargedEnergy != -1) { - addLiveViewValue(root, "dischargedEnergy", _dischargedEnergy, "kWh", 1); - } - addLiveViewTextValue(root, "chargeImmediately", (_chargeImmediately?"yes":"no")); - - if (_balance != -1) { - addLiveViewTextValue(root, "balancingActive", (_balance?"yes":"no")); - } - - addLiveViewInSection(root, "cells", "cellMinVoltage", static_cast(_cellMinMilliVolt)/1000, "V", 3); - addLiveViewInSection(root, "cells", "cellMaxVoltage", static_cast(_cellMaxMilliVolt)/1000, "V", 3); - addLiveViewInSection(root, "cells", "cellDiffVoltage", (_cellMaxMilliVolt - _cellMinMilliVolt), "mV", 0); - addLiveViewInSection(root, "cells", "cellMinTemperature", _cellMinTemperature, "°C", 0); - addLiveViewInSection(root, "cells", "cellMaxTemperature", _cellMaxTemperature, "°C", 0); - - addLiveViewTextInSection(root, "cells", "cellMinVoltageName", _cellMinVoltageName.c_str(), false); - addLiveViewTextInSection(root, "cells", "cellMaxVoltageName", _cellMaxVoltageName.c_str(), false); - addLiveViewTextInSection(root, "cells", "cellMinTemperatureName", _cellMinTemperatureName.c_str(), false); - addLiveViewTextInSection(root, "cells", "cellMaxTemperatureName", _cellMaxTemperatureName.c_str(), false); - - addLiveViewInSection(root, "modules", "online", _moduleCountOnline, "", 0); - addLiveViewInSection(root, "modules", "offline", _moduleCountOffline, "", 0); - addLiveViewInSection(root, "modules", "blockingCharge", _moduleCountBlockingCharge, "", 0); - addLiveViewInSection(root, "modules", "blockingDischarge", _moduleCountBlockingDischarge, "", 0); - - // alarms and warnings go into the "Issues" card of the web application - addLiveViewWarning(root, "highCurrentDischarge", _warningHighDischargeCurrent); - addLiveViewAlarm(root, "overCurrentDischarge", _alarmOverCurrentDischarge); - - addLiveViewWarning(root, "highCurrentCharge", _warningHighChargeCurrent); - addLiveViewAlarm(root, "overCurrentCharge", _alarmOverCurrentCharge); - - addLiveViewWarning(root, "lowVoltage", _warningLowVoltage); - addLiveViewAlarm(root, "underVoltage", _alarmUnderVoltage); - - addLiveViewWarning(root, "highVoltage", _warningHighVoltage); - addLiveViewAlarm(root, "overVoltage", _alarmOverVoltage); - - addLiveViewWarning(root, "lowTemperature", _warningLowTemperature); - addLiveViewAlarm(root, "underTemperature", _alarmUnderTemperature); - - addLiveViewWarning(root, "highTemperature", _warningHighTemperature); - addLiveViewAlarm(root, "overTemperature", _alarmOverTemperature); - - addLiveViewWarning(root, "lowTemperatureCharge", _warningLowTemperatureCharge); - addLiveViewAlarm(root, "underTemperatureCharge", _alarmUnderTemperatureCharge); - - addLiveViewWarning(root, "highTemperatureCharge", _warningHighTemperatureCharge); - addLiveViewAlarm(root, "overTemperatureCharge", _alarmOverTemperatureCharge); - - addLiveViewWarning(root, "bmsInternal", _warningInternalFailure); - addLiveViewAlarm(root, "bmsInternal", _alarmInternalFailure); - - addLiveViewWarning(root, "cellDiffVoltage", _warningCellImbalance); - addLiveViewAlarm(root, "cellDiffVoltage", _alarmCellImbalance); -} - -void JkBmsBatteryStats::getJsonData(JsonVariant& root, bool verbose) const -{ - BatteryStats::getLiveViewData(root); - - using Label = JkBms::DataPointLabel; - - auto oCurrent = _dataPoints.get(); - auto oVoltage = _dataPoints.get(); - if (oVoltage.has_value() && oCurrent.has_value()) { - auto current = static_cast(*oCurrent) / 1000; - auto voltage = static_cast(*oVoltage) / 1000; - addLiveViewValue(root, "power", current * voltage , "W", 2); - } - - auto oTemperatureBms = _dataPoints.get(); - if (oTemperatureBms.has_value()) { - addLiveViewValue(root, "bmsTemp", *oTemperatureBms, "°C", 0); - } - - // labels BatteryChargeEnabled, BatteryDischargeEnabled, and - // BalancingEnabled refer to the user setting. we want to show the - // actual MOSFETs' state which control whether charging and discharging - // is possible and whether the BMS is currently balancing cells. - auto oStatus = _dataPoints.get(); - if (oStatus.has_value()) { - using Bits = JkBms::StatusBits; - auto chargeEnabled = *oStatus & static_cast(Bits::ChargingActive); - addLiveViewTextValue(root, "chargeEnabled", (chargeEnabled?"yes":"no")); - auto dischargeEnabled = *oStatus & static_cast(Bits::DischargingActive); - addLiveViewTextValue(root, "dischargeEnabled", (dischargeEnabled?"yes":"no")); - } - - auto oTemperatureOne = _dataPoints.get(); - if (oTemperatureOne.has_value()) { - addLiveViewInSection(root, "cells", "batOneTemp", *oTemperatureOne, "°C", 0); - } - - auto oTemperatureTwo = _dataPoints.get(); - if (oTemperatureTwo.has_value()) { - addLiveViewInSection(root, "cells", "batTwoTemp", *oTemperatureTwo, "°C", 0); - } - - if (_cellVoltageTimestamp > 0) { - addLiveViewInSection(root, "cells", "cellMinVoltage", static_cast(_cellMinMilliVolt)/1000, "V", 3); - addLiveViewInSection(root, "cells", "cellAvgVoltage", static_cast(_cellAvgMilliVolt)/1000, "V", 3); - addLiveViewInSection(root, "cells", "cellMaxVoltage", static_cast(_cellMaxMilliVolt)/1000, "V", 3); - addLiveViewInSection(root, "cells", "cellDiffVoltage", (_cellMaxMilliVolt - _cellMinMilliVolt), "mV", 0); - } - - if (oStatus.has_value()) { - using Bits = JkBms::StatusBits; - auto balancingActive = *oStatus & static_cast(Bits::BalancingActive); - addLiveViewTextInSection(root, "cells", "balancingActive", (balancingActive?"yes":"no")); - } - - auto oAlarms = _dataPoints.get(); - if (oAlarms.has_value()) { -#define ISSUE(t, x) \ - auto x = *oAlarms & static_cast(JkBms::AlarmBits::x); \ - addLiveView##t(root, "JkBmsIssue"#x, x > 0); - - ISSUE(Warning, LowCapacity); - ISSUE(Alarm, BmsOvertemperature); - ISSUE(Alarm, ChargingOvervoltage); - ISSUE(Alarm, DischargeUndervoltage); - ISSUE(Alarm, BatteryOvertemperature); - ISSUE(Alarm, ChargingOvercurrent); - ISSUE(Alarm, DischargeOvercurrent); - ISSUE(Alarm, CellVoltageDifference); - ISSUE(Alarm, BatteryBoxOvertemperature); - ISSUE(Alarm, BatteryUndertemperature); - ISSUE(Alarm, CellOvervoltage); - ISSUE(Alarm, CellUndervoltage); - ISSUE(Alarm, AProtect); - ISSUE(Alarm, BProtect); -#undef ISSUE - } -} - -void JbdBmsBatteryStats::getJsonData(JsonVariant& root, bool verbose) const -{ - BatteryStats::getLiveViewData(root); - - using Label = JbdBms::DataPointLabel; - - auto oCurrent = _dataPoints.get(); - auto oVoltage = _dataPoints.get(); - if (oVoltage.has_value() && oCurrent.has_value()) { - auto current = static_cast(*oCurrent) / 1000; - auto voltage = static_cast(*oVoltage) / 1000; - addLiveViewValue(root, "power", current * voltage , "W", 2); - } - - auto oBatteryChargeEnabled = _dataPoints.get(); - if (oBatteryChargeEnabled.has_value()) { - addLiveViewTextValue(root, "chargeEnabled", (*oBatteryChargeEnabled?"yes":"no")); - } - - auto oBatteryDischargeEnabled = _dataPoints.get(); - if (oBatteryDischargeEnabled.has_value()) { - addLiveViewTextValue(root, "dischargeEnabled", (*oBatteryDischargeEnabled?"yes":"no")); - } - - auto oTemperatureOne = _dataPoints.get(); - if (oTemperatureOne.has_value()) { - addLiveViewInSection(root, "cells", "batOneTemp", *oTemperatureOne, "°C", 0); - } - - auto oTemperatureTwo = _dataPoints.get(); - if (oTemperatureTwo.has_value()) { - addLiveViewInSection(root, "cells", "batTwoTemp", *oTemperatureTwo, "°C", 0); - } - - if (_cellVoltageTimestamp > 0) { - addLiveViewInSection(root, "cells", "cellMinVoltage", static_cast(_cellMinMilliVolt)/1000, "V", 3); - addLiveViewInSection(root, "cells", "cellAvgVoltage", static_cast(_cellAvgMilliVolt)/1000, "V", 3); - addLiveViewInSection(root, "cells", "cellMaxVoltage", static_cast(_cellMaxMilliVolt)/1000, "V", 3); - addLiveViewInSection(root, "cells", "cellDiffVoltage", (_cellMaxMilliVolt - _cellMinMilliVolt), "mV", 0); - } - - auto oBalancingEnabled = _dataPoints.get(); - if (oBalancingEnabled.has_value()) { - addLiveViewTextInSection(root, "cells", "balancingActive", (*oBalancingEnabled?"yes":"no")); - } - - auto oAlarms = _dataPoints.get(); - if (oAlarms.has_value()) { -#define ISSUE(t, x) \ - auto x = *oAlarms & static_cast(JbdBms::AlarmBits::x); \ - addLiveView##t(root, "JbdBmsIssue"#x, x > 0); - - //ISSUE(Warning, LowCapacity); - ISSUE(Alarm, CellOverVoltage); - ISSUE(Alarm, CellUnderVoltage); - ISSUE(Alarm, PackOverVoltage); - ISSUE(Alarm, PackUnderVoltage); - ISSUE(Alarm, ChargingOverTemperature); - ISSUE(Alarm, ChargingLowTemperature); - ISSUE(Alarm, DischargingOverTemperature); - ISSUE(Alarm, DischargingLowTemperature); - ISSUE(Alarm, ChargingOverCurrent); - ISSUE(Alarm, DischargeOverCurrent); - ISSUE(Alarm, ShortCircuit); - ISSUE(Alarm, IcFrontEndError); - ISSUE(Alarm, MosSotwareLock); - ISSUE(Alarm, Reserved1); - ISSUE(Alarm, Reserved2); - ISSUE(Alarm, Reserved3); -#undef ISSUE - } -} - -void BatteryStats::mqttLoop() -{ - auto& config = Configuration.get(); - - if (!MqttSettings.getConnected() - || (millis() - _lastMqttPublish) < (config.Mqtt.PublishInterval * 1000)) { - return; - } - - mqttPublish(); - - _lastMqttPublish = millis(); -} - -uint32_t BatteryStats::getMqttFullPublishIntervalMs() const -{ - auto& config = Configuration.get(); - - // this is the default interval, see mqttLoop(). mqttPublish() - // implementations in derived classes may choose to publish some values - // with a lower frequency and hence implement this method with a different - // return value. - return config.Mqtt.PublishInterval * 1000; -} - -void BatteryStats::mqttPublish() const -{ - MqttSettings.publish("battery/manufacturer", _manufacturer); - MqttSettings.publish("battery/dataAge", String(getAgeSeconds())); - - if (isSoCValid()) { - MqttSettings.publish("battery/stateOfCharge", String(_soc)); - } - - if (isVoltageValid()) { - MqttSettings.publish("battery/voltage", String(_voltage)); - } - - if (isCurrentValid()) { - MqttSettings.publish("battery/current", String(_current)); - } - - if (isDischargeCurrentLimitValid()) { - MqttSettings.publish("battery/settings/dischargeCurrentLimitation", String(_dischargeCurrentLimit)); - } -} - -void PylontechBatteryStats::mqttPublish() const -{ - BatteryStats::mqttPublish(); - - MqttSettings.publish("battery/settings/chargeVoltage", String(_chargeVoltage)); - MqttSettings.publish("battery/settings/chargeCurrentLimitation", String(_chargeCurrentLimitation)); - MqttSettings.publish("battery/settings/dischargeVoltageLimitation", String(_dischargeVoltageLimitation)); - MqttSettings.publish("battery/stateOfHealth", String(_stateOfHealth)); - MqttSettings.publish("battery/temperature", String(_temperature)); - MqttSettings.publish("battery/alarm/overCurrentDischarge", String(_alarmOverCurrentDischarge)); - MqttSettings.publish("battery/alarm/overCurrentCharge", String(_alarmOverCurrentCharge)); - MqttSettings.publish("battery/alarm/underTemperature", String(_alarmUnderTemperature)); - MqttSettings.publish("battery/alarm/overTemperature", String(_alarmOverTemperature)); - MqttSettings.publish("battery/alarm/underVoltage", String(_alarmUnderVoltage)); - MqttSettings.publish("battery/alarm/overVoltage", String(_alarmOverVoltage)); - MqttSettings.publish("battery/alarm/bmsInternal", String(_alarmBmsInternal)); - MqttSettings.publish("battery/warning/highCurrentDischarge", String(_warningHighCurrentDischarge)); - MqttSettings.publish("battery/warning/highCurrentCharge", String(_warningHighCurrentCharge)); - MqttSettings.publish("battery/warning/lowTemperature", String(_warningLowTemperature)); - MqttSettings.publish("battery/warning/highTemperature", String(_warningHighTemperature)); - MqttSettings.publish("battery/warning/lowVoltage", String(_warningLowVoltage)); - MqttSettings.publish("battery/warning/highVoltage", String(_warningHighVoltage)); - MqttSettings.publish("battery/warning/bmsInternal", String(_warningBmsInternal)); - MqttSettings.publish("battery/charging/chargeEnabled", String(_chargeEnabled)); - MqttSettings.publish("battery/charging/dischargeEnabled", String(_dischargeEnabled)); - MqttSettings.publish("battery/charging/chargeImmediately", String(_chargeImmediately)); - MqttSettings.publish("battery/modulesTotal", String(_moduleCount)); -} - -void SBSBatteryStats::mqttPublish() const -{ - BatteryStats::mqttPublish(); - - MqttSettings.publish("battery/settings/chargeVoltage", String(_chargeVoltage)); - MqttSettings.publish("battery/settings/chargeCurrentLimitation", String(_chargeCurrentLimitation)); - MqttSettings.publish("battery/stateOfHealth", String(_stateOfHealth)); - MqttSettings.publish("battery/current", String(_current)); - MqttSettings.publish("battery/temperature", String(_temperature)); - MqttSettings.publish("battery/alarm/underVoltage", String(_alarmUnderVoltage)); - MqttSettings.publish("battery/alarm/overVoltage", String(_alarmOverVoltage)); - MqttSettings.publish("battery/alarm/bmsInternal", String(_alarmBmsInternal)); - MqttSettings.publish("battery/warning/highCurrentDischarge", String(_warningHighCurrentDischarge)); - MqttSettings.publish("battery/warning/highCurrentCharge", String(_warningHighCurrentCharge)); - MqttSettings.publish("battery/charging/chargeEnabled", String(_chargeEnabled)); - MqttSettings.publish("battery/charging/dischargeEnabled", String(_dischargeEnabled)); -} - -void PytesBatteryStats::mqttPublish() const -{ - BatteryStats::mqttPublish(); - - MqttSettings.publish("battery/settings/chargeVoltage", String(_chargeVoltageLimit)); - MqttSettings.publish("battery/settings/chargeCurrentLimitation", String(_chargeCurrentLimit)); - MqttSettings.publish("battery/settings/dischargeVoltageLimitation", String(_dischargeVoltageLimit)); - - MqttSettings.publish("battery/stateOfHealth", String(_stateOfHealth)); - if (_chargeCycles != -1) { - MqttSettings.publish("battery/chargeCycles", String(_chargeCycles)); - } - if (_balance != -1) { - MqttSettings.publish("battery/balancingActive", String(_balance ? 1 : 0)); - } - MqttSettings.publish("battery/temperature", String(_temperature)); - - if (_chargedEnergy != -1) { - MqttSettings.publish("battery/chargedEnergy", String(_chargedEnergy)); - } - - if (_dischargedEnergy != -1) { - MqttSettings.publish("battery/dischargedEnergy", String(_dischargedEnergy)); - } - - MqttSettings.publish("battery/capacity", String(_totalCapacity)); - MqttSettings.publish("battery/availableCapacity", String(_availableCapacity)); - - MqttSettings.publish("battery/CellMinMilliVolt", String(_cellMinMilliVolt)); - MqttSettings.publish("battery/CellMaxMilliVolt", String(_cellMaxMilliVolt)); - MqttSettings.publish("battery/CellDiffMilliVolt", String(_cellMaxMilliVolt - _cellMinMilliVolt)); - MqttSettings.publish("battery/CellMinTemperature", String(_cellMinTemperature)); - MqttSettings.publish("battery/CellMaxTemperature", String(_cellMaxTemperature)); - MqttSettings.publish("battery/CellMinVoltageName", String(_cellMinVoltageName)); - MqttSettings.publish("battery/CellMaxVoltageName", String(_cellMaxVoltageName)); - MqttSettings.publish("battery/CellMinTemperatureName", String(_cellMinTemperatureName)); - MqttSettings.publish("battery/CellMaxTemperatureName", String(_cellMaxTemperatureName)); - - MqttSettings.publish("battery/modulesOnline", String(_moduleCountOnline)); - MqttSettings.publish("battery/modulesOffline", String(_moduleCountOffline)); - MqttSettings.publish("battery/modulesBlockingCharge", String(_moduleCountBlockingCharge)); - MqttSettings.publish("battery/modulesBlockingDischarge", String(_moduleCountBlockingDischarge)); - - MqttSettings.publish("battery/alarm/overCurrentDischarge", String(_alarmOverCurrentDischarge)); - MqttSettings.publish("battery/alarm/overCurrentCharge", String(_alarmOverCurrentCharge)); - MqttSettings.publish("battery/alarm/underVoltage", String(_alarmUnderVoltage)); - MqttSettings.publish("battery/alarm/overVoltage", String(_alarmOverVoltage)); - MqttSettings.publish("battery/alarm/underTemperature", String(_alarmUnderTemperature)); - MqttSettings.publish("battery/alarm/overTemperature", String(_alarmOverTemperature)); - MqttSettings.publish("battery/alarm/underTemperatureCharge", String(_alarmUnderTemperatureCharge)); - MqttSettings.publish("battery/alarm/overTemperatureCharge", String(_alarmOverTemperatureCharge)); - MqttSettings.publish("battery/alarm/bmsInternal", String(_alarmInternalFailure)); - MqttSettings.publish("battery/alarm/cellImbalance", String(_alarmCellImbalance)); - - MqttSettings.publish("battery/warning/highCurrentDischarge", String(_warningHighDischargeCurrent)); - MqttSettings.publish("battery/warning/highCurrentCharge", String(_warningHighChargeCurrent)); - MqttSettings.publish("battery/warning/lowVoltage", String(_warningLowVoltage)); - MqttSettings.publish("battery/warning/highVoltage", String(_warningHighVoltage)); - MqttSettings.publish("battery/warning/lowTemperature", String(_warningLowTemperature)); - MqttSettings.publish("battery/warning/highTemperature", String(_warningHighTemperature)); - MqttSettings.publish("battery/warning/lowTemperatureCharge", String(_warningLowTemperatureCharge)); - MqttSettings.publish("battery/warning/highTemperatureCharge", String(_warningHighTemperatureCharge)); - MqttSettings.publish("battery/warning/bmsInternal", String(_warningInternalFailure)); - MqttSettings.publish("battery/warning/cellImbalance", String(_warningCellImbalance)); - - MqttSettings.publish("battery/charging/chargeImmediately", String(_chargeImmediately)); -} - -void JkBmsBatteryStats::mqttPublish() const -{ - BatteryStats::mqttPublish(); - - using Label = JkBms::DataPointLabel; - - static std::vector