this change utilizes some of the features from library "frozen", which was included upstream for the grid profile parser. to improve code maintainability, a couple of std::maps mapping strings to values or the other way around were introduced in OpenDTU-OnBattery-specific code at the expense of some flash and computing overhead. library "frozen" offers constexpr versions of map and string, which saves initialization code and offers slightly faster lookups. this brings the binary size down by ~25kB and should provide a small performance improvement at runtime.
305 lines
12 KiB
C++
305 lines
12 KiB
C++
#pragma once
|
|
|
|
#include <Arduino.h>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <variant>
|
|
#include <frozen/map.h>
|
|
#include <frozen/string.h>
|
|
|
|
namespace JkBms {
|
|
|
|
#define ALARM_BITS(fnc) \
|
|
fnc(LowCapacity, (1<<0)) \
|
|
fnc(BmsOvertemperature, (1<<1)) \
|
|
fnc(ChargingOvervoltage, (1<<2)) \
|
|
fnc(DischargeUndervoltage, (1<<3)) \
|
|
fnc(BatteryOvertemperature, (1<<4)) \
|
|
fnc(ChargingOvercurrent, (1<<5)) \
|
|
fnc(DischargeOvercurrent, (1<<6)) \
|
|
fnc(CellVoltageDifference, (1<<7)) \
|
|
fnc(BatteryBoxOvertemperature, (1<<8)) \
|
|
fnc(BatteryUndertemperature, (1<<9)) \
|
|
fnc(CellOvervoltage, (1<<10)) \
|
|
fnc(CellUndervoltage, (1<<11)) \
|
|
fnc(AProtect, (1<<12)) \
|
|
fnc(BProtect, (1<<13)) \
|
|
fnc(Reserved1, (1<<14)) \
|
|
fnc(Reserved2, (1<<15))
|
|
|
|
enum class AlarmBits : uint16_t {
|
|
#define ALARM_ENUM(name, value) name = value,
|
|
ALARM_BITS(ALARM_ENUM)
|
|
#undef ALARM_ENUM
|
|
};
|
|
|
|
static const frozen::map<AlarmBits, frozen::string, 16> AlarmBitTexts = {
|
|
#define ALARM_TEXT(name, value) { AlarmBits::name, #name },
|
|
ALARM_BITS(ALARM_TEXT)
|
|
#undef ALARM_TEXT
|
|
};
|
|
|
|
#define STATUS_BITS(fnc) \
|
|
fnc(ChargingActive, (1<<0)) \
|
|
fnc(DischargingActive, (1<<1)) \
|
|
fnc(BalancingActive, (1<<2)) \
|
|
fnc(BatteryOnline, (1<<3))
|
|
|
|
enum class StatusBits : uint16_t {
|
|
#define STATUS_ENUM(name, value) name = value,
|
|
STATUS_BITS(STATUS_ENUM)
|
|
#undef STATUS_ENUM
|
|
};
|
|
|
|
static const frozen::map<StatusBits, frozen::string, 4> StatusBitTexts = {
|
|
#define STATUS_TEXT(name, value) { StatusBits::name, #name },
|
|
STATUS_BITS(STATUS_TEXT)
|
|
#undef STATUS_TEXT
|
|
};
|
|
|
|
enum class DataPointLabel : uint8_t {
|
|
CellsMilliVolt = 0x79,
|
|
BmsTempCelsius = 0x80,
|
|
BatteryTempOneCelsius = 0x81,
|
|
BatteryTempTwoCelsius = 0x82,
|
|
BatteryVoltageMilliVolt = 0x83,
|
|
BatteryCurrentMilliAmps = 0x84,
|
|
BatterySoCPercent = 0x85,
|
|
BatteryTemperatureSensorAmount = 0x86,
|
|
BatteryCycles = 0x87,
|
|
BatteryCycleCapacity = 0x89,
|
|
BatteryCellAmount = 0x8a,
|
|
AlarmsBitmask = 0x8b,
|
|
StatusBitmask = 0x8c,
|
|
TotalOvervoltageThresholdMilliVolt = 0x8e,
|
|
TotalUndervoltageThresholdMilliVolt = 0x8f,
|
|
CellOvervoltageThresholdMilliVolt = 0x90,
|
|
CellOvervoltageRecoveryMilliVolt = 0x91,
|
|
CellOvervoltageProtectionDelaySeconds = 0x92,
|
|
CellUndervoltageThresholdMilliVolt = 0x93,
|
|
CellUndervoltageRecoveryMilliVolt = 0x94,
|
|
CellUndervoltageProtectionDelaySeconds = 0x95,
|
|
CellVoltageDiffThresholdMilliVolt = 0x96,
|
|
DischargeOvercurrentThresholdAmperes = 0x97,
|
|
DischargeOvercurrentDelaySeconds = 0x98,
|
|
ChargeOvercurrentThresholdAmps = 0x99,
|
|
ChargeOvercurrentDelaySeconds = 0x9a,
|
|
BalanceCellVoltageThresholdMilliVolt = 0x9b,
|
|
BalanceVoltageDiffThresholdMilliVolt = 0x9c,
|
|
BalancingEnabled = 0x9d,
|
|
BmsTempProtectionThresholdCelsius = 0x9e,
|
|
BmsTempRecoveryThresholdCelsius = 0x9f,
|
|
BatteryTempProtectionThresholdCelsius = 0xa0,
|
|
BatteryTempRecoveryThresholdCelsius = 0xa1,
|
|
BatteryTempDiffThresholdCelsius = 0xa2,
|
|
ChargeHighTempThresholdCelsius = 0xa3,
|
|
DischargeHighTempThresholdCelsius = 0xa4,
|
|
ChargeLowTempThresholdCelsius = 0xa5,
|
|
ChargeLowTempRecoveryCelsius = 0xa6,
|
|
DischargeLowTempThresholdCelsius = 0xa7,
|
|
DischargeLowTempRecoveryCelsius = 0xa8,
|
|
CellAmountSetting = 0xa9,
|
|
BatteryCapacitySettingAmpHours = 0xaa,
|
|
BatteryChargeEnabled = 0xab,
|
|
BatteryDischargeEnabled = 0xac,
|
|
CurrentCalibrationMilliAmps = 0xad,
|
|
BmsAddress = 0xae,
|
|
BatteryType = 0xaf,
|
|
SleepWaitTime = 0xb0, // what's this?
|
|
LowCapacityAlarmThresholdPercent = 0xb1,
|
|
ModificationPassword = 0xb2,
|
|
DedicatedChargerSwitch = 0xb3, // what's this?
|
|
EquipmentId = 0xb4,
|
|
DateOfManufacturing = 0xb5,
|
|
BmsHourMeterMinutes = 0xb6,
|
|
BmsSoftwareVersion = 0xb7,
|
|
CurrentCalibration = 0xb8,
|
|
ActualBatteryCapacityAmpHours = 0xb9,
|
|
ProductId = 0xba,
|
|
ProtocolVersion = 0xc0
|
|
};
|
|
|
|
using tCells = std::map<uint8_t, uint16_t>;
|
|
|
|
template<DataPointLabel> struct DataPointLabelTraits;
|
|
|
|
#define LABEL_TRAIT(n, t, u) template<> struct DataPointLabelTraits<DataPointLabel::n> { \
|
|
using type = t; \
|
|
static constexpr char const name[] = #n; \
|
|
static constexpr char const unit[] = u; \
|
|
};
|
|
|
|
/**
|
|
* the types associated with the labels are the types for the respective data
|
|
* points in the JkBms::DataPoint class. they are *not* always equal to the
|
|
* type used in the serial message.
|
|
*
|
|
* it is unfortunate that we have to repeat all enum values here to define the
|
|
* traits. code generation could help here (labels are defined in a single
|
|
* source of truth and this code is generated -- no typing errors, etc.).
|
|
* however, the compiler will complain if an enum is misspelled or traits are
|
|
* defined for a removed enum, so we will notice. it will also complain when a
|
|
* trait is missing and if a data point for a label without traits is added to
|
|
* the DataPointContainer class, because the traits must be available then.
|
|
* even though this is tedious to maintain, human errors will be caught.
|
|
*/
|
|
LABEL_TRAIT(CellsMilliVolt, tCells, "mV");
|
|
LABEL_TRAIT(BmsTempCelsius, int16_t, "°C");
|
|
LABEL_TRAIT(BatteryTempOneCelsius, int16_t, "°C");
|
|
LABEL_TRAIT(BatteryTempTwoCelsius, int16_t, "°C");
|
|
LABEL_TRAIT(BatteryVoltageMilliVolt, uint32_t, "mV");
|
|
LABEL_TRAIT(BatteryCurrentMilliAmps, int32_t, "mA");
|
|
LABEL_TRAIT(BatterySoCPercent, uint8_t, "%");
|
|
LABEL_TRAIT(BatteryTemperatureSensorAmount, uint8_t, "");
|
|
LABEL_TRAIT(BatteryCycles, uint16_t, "");
|
|
LABEL_TRAIT(BatteryCycleCapacity, uint32_t, "Ah");
|
|
LABEL_TRAIT(BatteryCellAmount, uint16_t, "");
|
|
LABEL_TRAIT(AlarmsBitmask, uint16_t, "");
|
|
LABEL_TRAIT(StatusBitmask, uint16_t, "");
|
|
LABEL_TRAIT(TotalOvervoltageThresholdMilliVolt, uint32_t, "mV");
|
|
LABEL_TRAIT(TotalUndervoltageThresholdMilliVolt, uint32_t, "mV");
|
|
LABEL_TRAIT(CellOvervoltageThresholdMilliVolt, uint16_t, "mV");
|
|
LABEL_TRAIT(CellOvervoltageRecoveryMilliVolt, uint16_t, "mV");
|
|
LABEL_TRAIT(CellOvervoltageProtectionDelaySeconds, uint16_t, "s");
|
|
LABEL_TRAIT(CellUndervoltageThresholdMilliVolt, uint16_t, "mV");
|
|
LABEL_TRAIT(CellUndervoltageRecoveryMilliVolt, uint16_t, "mV");
|
|
LABEL_TRAIT(CellUndervoltageProtectionDelaySeconds, uint16_t, "s");
|
|
LABEL_TRAIT(CellVoltageDiffThresholdMilliVolt, uint16_t, "mV");
|
|
LABEL_TRAIT(DischargeOvercurrentThresholdAmperes, uint16_t, "A");
|
|
LABEL_TRAIT(DischargeOvercurrentDelaySeconds, uint16_t, "s");
|
|
LABEL_TRAIT(ChargeOvercurrentThresholdAmps, uint16_t, "A");
|
|
LABEL_TRAIT(ChargeOvercurrentDelaySeconds, uint16_t, "s");
|
|
LABEL_TRAIT(BalanceCellVoltageThresholdMilliVolt, uint16_t, "mV");
|
|
LABEL_TRAIT(BalanceVoltageDiffThresholdMilliVolt, uint16_t, "mV");
|
|
LABEL_TRAIT(BalancingEnabled, bool, "");
|
|
LABEL_TRAIT(BmsTempProtectionThresholdCelsius, uint16_t, "°C");
|
|
LABEL_TRAIT(BmsTempRecoveryThresholdCelsius, uint16_t, "°C");
|
|
LABEL_TRAIT(BatteryTempProtectionThresholdCelsius, uint16_t, "°C");
|
|
LABEL_TRAIT(BatteryTempRecoveryThresholdCelsius, uint16_t, "°C");
|
|
LABEL_TRAIT(BatteryTempDiffThresholdCelsius, uint16_t, "°C");
|
|
LABEL_TRAIT(ChargeHighTempThresholdCelsius, uint16_t, "°C");
|
|
LABEL_TRAIT(DischargeHighTempThresholdCelsius, uint16_t, "°C");
|
|
LABEL_TRAIT(ChargeLowTempThresholdCelsius, int16_t, "°C");
|
|
LABEL_TRAIT(ChargeLowTempRecoveryCelsius, int16_t, "°C");
|
|
LABEL_TRAIT(DischargeLowTempThresholdCelsius, int16_t, "°C");
|
|
LABEL_TRAIT(DischargeLowTempRecoveryCelsius, int16_t, "°C");
|
|
LABEL_TRAIT(CellAmountSetting, uint8_t, "");
|
|
LABEL_TRAIT(BatteryCapacitySettingAmpHours, uint32_t, "Ah");
|
|
LABEL_TRAIT(BatteryChargeEnabled, bool, "");
|
|
LABEL_TRAIT(BatteryDischargeEnabled, bool, "");
|
|
LABEL_TRAIT(CurrentCalibrationMilliAmps, uint16_t, "mA");
|
|
LABEL_TRAIT(BmsAddress, uint8_t, "");
|
|
LABEL_TRAIT(BatteryType, uint8_t, "");
|
|
LABEL_TRAIT(SleepWaitTime, uint16_t, "s");
|
|
LABEL_TRAIT(LowCapacityAlarmThresholdPercent, uint8_t, "%");
|
|
LABEL_TRAIT(ModificationPassword, std::string, "");
|
|
LABEL_TRAIT(DedicatedChargerSwitch, bool, "");
|
|
LABEL_TRAIT(EquipmentId, std::string, "");
|
|
LABEL_TRAIT(DateOfManufacturing, std::string, "");
|
|
LABEL_TRAIT(BmsHourMeterMinutes, uint32_t, "min");
|
|
LABEL_TRAIT(BmsSoftwareVersion, std::string, "");
|
|
LABEL_TRAIT(CurrentCalibration, bool, "");
|
|
LABEL_TRAIT(ActualBatteryCapacityAmpHours, uint32_t, "Ah");
|
|
LABEL_TRAIT(ProductId, std::string, "");
|
|
LABEL_TRAIT(ProtocolVersion, uint8_t, "");
|
|
#undef LABEL_TRAIT
|
|
|
|
class DataPoint {
|
|
friend class DataPointContainer;
|
|
|
|
public:
|
|
using tValue = std::variant<bool, uint8_t, uint16_t, uint32_t,
|
|
int16_t, int32_t, std::string, tCells>;
|
|
|
|
DataPoint() = delete;
|
|
|
|
DataPoint(DataPoint const& other)
|
|
: _strLabel(other._strLabel)
|
|
, _strValue(other._strValue)
|
|
, _strUnit(other._strUnit)
|
|
, _value(other._value)
|
|
, _timestamp(other._timestamp) { }
|
|
|
|
DataPoint(std::string const& strLabel, std::string const& strValue,
|
|
std::string const& strUnit, tValue value, uint32_t timestamp)
|
|
: _strLabel(strLabel)
|
|
, _strValue(strValue)
|
|
, _strUnit(strUnit)
|
|
, _value(std::move(value))
|
|
, _timestamp(timestamp) { }
|
|
|
|
std::string const& getLabelText() const { return _strLabel; }
|
|
std::string const& getValueText() const { return _strValue; }
|
|
std::string const& getUnitText() const { return _strUnit; }
|
|
uint32_t getTimestamp() const { return _timestamp; }
|
|
|
|
bool operator==(DataPoint const& other) const {
|
|
return _value == other._value;
|
|
}
|
|
|
|
private:
|
|
std::string _strLabel;
|
|
std::string _strValue;
|
|
std::string _strUnit;
|
|
tValue _value;
|
|
uint32_t _timestamp;
|
|
};
|
|
|
|
template<typename T> std::string dataPointValueToStr(T const& v);
|
|
|
|
class DataPointContainer {
|
|
public:
|
|
DataPointContainer() = default;
|
|
|
|
using Label = DataPointLabel;
|
|
template<Label L> using Traits = JkBms::DataPointLabelTraits<L>;
|
|
|
|
template<Label L>
|
|
void add(typename Traits<L>::type val) {
|
|
_dataPoints.emplace(
|
|
L,
|
|
DataPoint(
|
|
Traits<L>::name,
|
|
dataPointValueToStr(val),
|
|
Traits<L>::unit,
|
|
DataPoint::tValue(std::move(val)),
|
|
millis()
|
|
)
|
|
);
|
|
}
|
|
|
|
// make sure add() is only called with the type expected for the
|
|
// respective label, no implicit conversions allowed.
|
|
template<Label L, typename T>
|
|
void add(T) = delete;
|
|
|
|
template<Label L>
|
|
std::optional<DataPoint const> getDataPointFor() const {
|
|
auto it = _dataPoints.find(L);
|
|
if (it == _dataPoints.end()) { return std::nullopt; }
|
|
return it->second;
|
|
}
|
|
|
|
template<Label L>
|
|
std::optional<typename Traits<L>::type> get() const {
|
|
auto optionalDataPoint = getDataPointFor<L>();
|
|
if (!optionalDataPoint.has_value()) { return std::nullopt; }
|
|
return std::get<typename Traits<L>::type>(optionalDataPoint->_value);
|
|
}
|
|
|
|
using tMap = std::unordered_map<Label, DataPoint const>;
|
|
tMap::const_iterator cbegin() const { return _dataPoints.cbegin(); }
|
|
tMap::const_iterator cend() const { return _dataPoints.cend(); }
|
|
|
|
// copy all data points from source into this instance, overwriting
|
|
// existing data points in this instance.
|
|
void updateFrom(DataPointContainer const& source);
|
|
|
|
private:
|
|
tMap _dataPoints;
|
|
};
|
|
|
|
} /* namespace JkBms */
|