Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jbd bms support #1218

Merged
merged 4 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions include/BatteryStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "AsyncJson.h"
#include "Arduino.h"
#include "JkBmsDataPoints.h"
#include "JbdBmsDataPoints.h"
#include "VeDirectShuntController.h"
#include <cfloat>

Expand Down Expand Up @@ -283,6 +284,35 @@ class JkBmsBatteryStats : public BatteryStats {
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;
Expand Down
119 changes: 119 additions & 0 deletions include/DataPoints.h
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;
};
87 changes: 87 additions & 0 deletions include/JbdBmsController.h
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 */
118 changes: 118 additions & 0 deletions include/JbdBmsDataPoints.h
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 */
Loading