forked from tbnobody/OpenDTU
-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: support for JBD BMS using serial connection
- Loading branch information
1 parent
33b7697
commit 7cd1984
Showing
16 changed files
with
1,241 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
#pragma once | ||
|
||
#include <Arduino.h> | ||
#include <map> | ||
#include <optional> | ||
#include <string> | ||
#include <unordered_map> | ||
#include <variant> | ||
|
||
using tCellVoltages = std::map<uint8_t, uint16_t>; | ||
|
||
template<typename... V> | ||
class DataPoint { | ||
template<typename, typename L, template<L> class> | ||
friend class DataPointContainer; | ||
|
||
public: | ||
using tValue = std::variant<V...>; | ||
|
||
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); | ||
|
||
template<typename DataPoint, typename Label, template<Label> class Traits> | ||
class DataPointContainer { | ||
public: | ||
DataPointContainer() = default; | ||
|
||
//template<Label L> using Traits = LabelTraits<L>; | ||
|
||
template<Label L> | ||
void add(typename Traits<L>::type val) { | ||
_dataPoints.emplace( | ||
L, | ||
DataPoint( | ||
Traits<L>::name, | ||
dataPointValueToStr(val), | ||
Traits<L>::unit, | ||
typename 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>; | ||
typename tMap::const_iterator cbegin() const { return _dataPoints.cbegin(); } | ||
typename 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) | ||
{ | ||
for (auto iter = source.cbegin(); iter != source.cend(); ++iter) { | ||
auto pos = _dataPoints.find(iter->first); | ||
|
||
if (pos != _dataPoints.end()) { | ||
// do not update existing data points with the same value | ||
if (pos->second == iter->second) { continue; } | ||
|
||
_dataPoints.erase(pos); | ||
} | ||
_dataPoints.insert(*iter); | ||
} | ||
} | ||
|
||
private: | ||
tMap _dataPoints; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
#pragma once | ||
|
||
#include <memory> | ||
#include <vector> | ||
#include <frozen/string.h> | ||
|
||
#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<BatteryStats> getStats() const final { return _stats; } | ||
|
||
private: | ||
static char constexpr _serialPortOwner[] = "JBD BMS"; | ||
|
||
#ifdef JBDBMS_DUMMY_SERIAL | ||
std::unique_ptr<DummySerial> _upSerial; | ||
#else | ||
std::unique_ptr<HardwareSerial> _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<JbdBmsBatteryStats> _stats = | ||
std::make_shared<JbdBmsBatteryStats>(); | ||
}; | ||
|
||
} /* namespace JbdBms */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
#pragma once | ||
|
||
#include <Arduino.h> | ||
#include <map> | ||
#include <frozen/map.h> | ||
#include <frozen/string.h> | ||
|
||
#include "DataPoints.h" | ||
|
||
namespace JbdBms { | ||
|
||
#define JBD_PROTECTION_STATUS(fnc) \ | ||
fnc(CellOverVoltage, (1<<0)) \ | ||
fnc(CellUnderVoltage, (1<<1)) \ | ||
fnc(PackOverVoltage, (1<<2)) \ | ||
fnc(PackUnderVoltage, (1<<3)) \ | ||
fnc(ChargingOverTemperature, (1<<4)) \ | ||
fnc(ChargingLowTemperature, (1<<5)) \ | ||
fnc(DischargingOverTemperature, (1<<6)) \ | ||
fnc(DischargingLowTemperature, (1<<7)) \ | ||
fnc(ChargingOverCurrent, (1<<8)) \ | ||
fnc(DischargeOverCurrent, (1<<9)) \ | ||
fnc(ShortCircuit, (1<<10)) \ | ||
fnc(IcFrontEndError, (1<<11)) \ | ||
fnc(MosSotwareLock, (1<<12)) \ | ||
fnc(Reserved1, (1<<13)) \ | ||
fnc(Reserved2, (1<<14)) \ | ||
fnc(Reserved3, (1<<15)) | ||
|
||
enum class AlarmBits : uint16_t { | ||
#define ALARM_ENUM(name, value) name = value, | ||
JBD_PROTECTION_STATUS(ALARM_ENUM) | ||
#undef ALARM_ENUM | ||
}; | ||
|
||
static const frozen::map<AlarmBits, frozen::string, 16> AlarmBitTexts = { | ||
#define ALARM_TEXT(name, value) { AlarmBits::name, #name }, | ||
JBD_PROTECTION_STATUS(ALARM_TEXT) | ||
#undef ALARM_TEXT | ||
}; | ||
|
||
enum class DataPointLabel : uint8_t { | ||
CellsMilliVolt, | ||
BatteryTempOneCelsius, | ||
BatteryTempTwoCelsius, | ||
BatteryVoltageMilliVolt, | ||
BatteryCurrentMilliAmps, | ||
BatterySoCPercent, | ||
BatteryTemperatureSensorAmount, | ||
BatteryCycles, | ||
BatteryCellAmount, | ||
AlarmsBitmask, | ||
BalancingEnabled, | ||
CellAmountSetting, | ||
BatteryCapacitySettingAmpHours, | ||
BatteryChargeEnabled, | ||
BatteryDischargeEnabled, | ||
DateOfManufacturing, | ||
BmsSoftwareVersion, | ||
BmsHardwareVersion, | ||
ActualBatteryCapacityAmpHours | ||
}; | ||
|
||
using tCells = tCellVoltages; | ||
|
||
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 JbdBms::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(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(BatteryCellAmount, uint16_t, ""); | ||
LABEL_TRAIT(AlarmsBitmask, uint16_t, ""); | ||
LABEL_TRAIT(BalancingEnabled, bool, ""); | ||
LABEL_TRAIT(CellAmountSetting, uint8_t, ""); | ||
LABEL_TRAIT(BatteryCapacitySettingAmpHours, uint32_t, "Ah"); | ||
LABEL_TRAIT(BatteryChargeEnabled, bool, ""); | ||
LABEL_TRAIT(BatteryDischargeEnabled, bool, ""); | ||
LABEL_TRAIT(DateOfManufacturing, std::string, ""); | ||
LABEL_TRAIT(BmsSoftwareVersion, std::string, ""); | ||
LABEL_TRAIT(BmsHardwareVersion, std::string, ""); | ||
LABEL_TRAIT(ActualBatteryCapacityAmpHours, uint32_t, "Ah"); | ||
#undef LABEL_TRAIT | ||
|
||
} /* namespace JbdBms */ | ||
|
||
using JbdBmsDataPoint = DataPoint<bool, uint8_t, uint16_t, uint32_t, | ||
int16_t, int32_t, std::string, JbdBms::tCells>; | ||
|
||
template class DataPointContainer<JbdBmsDataPoint, JbdBms::DataPointLabel, JbdBms::DataPointLabelTraits>; | ||
|
||
namespace JbdBms { | ||
using DataPointContainer = DataPointContainer<JbdBmsDataPoint, DataPointLabel, DataPointLabelTraits>; | ||
} /* namespace JbdBms */ |
Oops, something went wrong.