Skip to content

Commit 9ff39fd

Browse files
committed
Merge branch 'development'
2 parents 0c829a5 + fb2ca28 commit 9ff39fd

File tree

8 files changed

+333
-14
lines changed

8 files changed

+333
-14
lines changed

include/BatteryStats.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,29 @@ class PylontechBatteryStats : public BatteryStats {
8989

9090
class JkBmsBatteryStats : public BatteryStats {
9191
public:
92-
void getLiveViewData(JsonVariant& root) const final;
92+
void getLiveViewData(JsonVariant& root) const final {
93+
getJsonData(root, false);
94+
}
95+
96+
void getInfoViewData(JsonVariant& root) const {
97+
getJsonData(root, true);
98+
}
99+
93100
void mqttPublish() const final;
94101

95102
void updateFrom(JkBms::DataPointContainer const& dp);
96103

97104
private:
105+
void getJsonData(JsonVariant& root, bool verbose) const;
106+
98107
JkBms::DataPointContainer _dataPoints;
99108
mutable uint32_t _lastMqttPublish = 0;
100109
mutable uint32_t _lastFullMqttPublish = 0;
110+
111+
uint16_t _cellMinMilliVolt = 0;
112+
uint16_t _cellAvgMilliVolt = 0;
113+
uint16_t _cellMaxMilliVolt = 0;
114+
uint32_t _cellVoltageTimestamp = 0;
101115
};
102116

103117
class VictronSmartShuntStats : public BatteryStats {
@@ -107,8 +121,8 @@ class VictronSmartShuntStats : public BatteryStats {
107121

108122
void updateFrom(VeDirectShuntController::veShuntStruct const& shuntData);
109123

110-
private:
111-
float _voltage;
124+
private:
125+
float _voltage;
112126
float _current;
113127
float _temperature;
114128
bool _tempPresent;

include/JkBmsDataPoints.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,54 @@
99

1010
namespace JkBms {
1111

12+
#define ALARM_BITS(fnc) \
13+
fnc(LowCapacity, (1<<0)) \
14+
fnc(BmsOvertemperature, (1<<1)) \
15+
fnc(ChargingOvervoltage, (1<<2)) \
16+
fnc(DischargeUndervoltage, (1<<3)) \
17+
fnc(BatteryOvertemperature, (1<<4)) \
18+
fnc(ChargingOvercurrent, (1<<5)) \
19+
fnc(DischargeOvercurrent, (1<<6)) \
20+
fnc(CellVoltageDifference, (1<<7)) \
21+
fnc(BatteryBoxOvertemperature, (1<<8)) \
22+
fnc(BatteryUndertemperature, (1<<9)) \
23+
fnc(CellOvervoltage, (1<<10)) \
24+
fnc(CellUndervoltage, (1<<11)) \
25+
fnc(AProtect, (1<<12)) \
26+
fnc(BProtect, (1<<13)) \
27+
fnc(Reserved1, (1<<14)) \
28+
fnc(Reserved2, (1<<15))
29+
30+
enum class AlarmBits : uint16_t {
31+
#define ALARM_ENUM(name, value) name = value,
32+
ALARM_BITS(ALARM_ENUM)
33+
#undef ALARM_ENUM
34+
};
35+
36+
static const std::map<AlarmBits, std::string> AlarmBitTexts = {
37+
#define ALARM_TEXT(name, value) { AlarmBits::name, #name },
38+
ALARM_BITS(ALARM_TEXT)
39+
#undef ALARM_TEXT
40+
};
41+
42+
#define STATUS_BITS(fnc) \
43+
fnc(ChargingActive, (1<<0)) \
44+
fnc(DischargingActive, (1<<1)) \
45+
fnc(BalancingActive, (1<<2)) \
46+
fnc(BatteryOnline, (1<<3))
47+
48+
enum class StatusBits : uint16_t {
49+
#define STATUS_ENUM(name, value) name = value,
50+
STATUS_BITS(STATUS_ENUM)
51+
#undef STATUS_ENUM
52+
};
53+
54+
static const std::map<StatusBits, std::string> StatusBitTexts = {
55+
#define STATUS_TEXT(name, value) { StatusBits::name, #name },
56+
STATUS_BITS(STATUS_TEXT)
57+
#undef STATUS_TEXT
58+
};
59+
1260
enum class DataPointLabel : uint8_t {
1361
CellsMilliVolt = 0x79,
1462
BmsTempCelsius = 0x80,

src/BatteryStats.cpp

Lines changed: 130 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ void PylontechBatteryStats::getLiveViewData(JsonVariant& root) const
8484
addLiveViewAlarm(root, "bmsInternal", _alarmBmsInternal);
8585
}
8686

87-
void JkBmsBatteryStats::getLiveViewData(JsonVariant& root) const
87+
void JkBmsBatteryStats::getJsonData(JsonVariant& root, bool verbose) const
8888
{
8989
BatteryStats::getLiveViewData(root);
9090

@@ -102,9 +102,80 @@ void JkBmsBatteryStats::getLiveViewData(JsonVariant& root) const
102102
static_cast<float>(*oCurrent) / 1000, "A", 2);
103103
}
104104

105-
auto oTemperature = _dataPoints.get<Label::BatteryTempOneCelsius>();
106-
if (oTemperature.has_value()) {
107-
addLiveViewValue(root, "temperature", *oTemperature, "°C", 0);
105+
if (oVoltage.has_value() && oCurrent.has_value()) {
106+
auto current = static_cast<float>(*oCurrent) / 1000;
107+
auto voltage = static_cast<float>(*oVoltage) / 1000;
108+
addLiveViewValue(root, "power", current * voltage , "W", 2);
109+
}
110+
111+
if (verbose) {
112+
auto oTemperatureOne = _dataPoints.get<Label::BatteryTempOneCelsius>();
113+
if (oTemperatureOne.has_value()) {
114+
addLiveViewValue(root, "batOneTemp", *oTemperatureOne, "°C", 0);
115+
}
116+
}
117+
118+
if (verbose) {
119+
auto oTemperatureTwo = _dataPoints.get<Label::BatteryTempTwoCelsius>();
120+
if (oTemperatureTwo.has_value()) {
121+
addLiveViewValue(root, "batTwoTemp", *oTemperatureTwo, "°C", 0);
122+
}
123+
}
124+
125+
auto oTemperatureBms = _dataPoints.get<Label::BmsTempCelsius>();
126+
if (oTemperatureBms.has_value()) {
127+
addLiveViewValue(root, "bmsTemp", *oTemperatureBms, "°C", 0);
128+
}
129+
130+
if (_cellVoltageTimestamp > 0) {
131+
if (verbose) {
132+
addLiveViewValue(root, "cellMinVoltage", static_cast<float>(_cellMinMilliVolt)/1000, "V", 3);
133+
}
134+
135+
addLiveViewValue(root, "cellAvgVoltage", static_cast<float>(_cellAvgMilliVolt)/1000, "V", 3);
136+
137+
if (verbose) {
138+
addLiveViewValue(root, "cellMaxVoltage", static_cast<float>(_cellMaxMilliVolt)/1000, "V", 3);
139+
addLiveViewValue(root, "cellDiffVoltage", (_cellMaxMilliVolt - _cellMinMilliVolt), "mV", 0);
140+
}
141+
}
142+
143+
// labels BatteryChargeEnabled, BatteryDischargeEnabled, and
144+
// BalancingEnabled refer to the user setting. we want to show the
145+
// actual MOSFETs' state which control whether charging and discharging
146+
// is possible and whether the BMS is currently balancing cells.
147+
auto oStatus = _dataPoints.get<Label::StatusBitmask>();
148+
if (oStatus.has_value()) {
149+
using Bits = JkBms::StatusBits;
150+
auto chargeEnabled = *oStatus & static_cast<uint16_t>(Bits::ChargingActive);
151+
addLiveViewText(root, "chargeEnabled", (chargeEnabled?"yes":"no"));
152+
auto dischargeEnabled = *oStatus & static_cast<uint16_t>(Bits::DischargingActive);
153+
addLiveViewText(root, "dischargeEnabled", (dischargeEnabled?"yes":"no"));
154+
auto balancingActive = *oStatus & static_cast<uint16_t>(Bits::BalancingActive);
155+
addLiveViewText(root, "balancingActive", (balancingActive?"yes":"no"));
156+
}
157+
158+
auto oAlarms = _dataPoints.get<Label::AlarmsBitmask>();
159+
if (oAlarms.has_value()) {
160+
#define ISSUE(t, x) \
161+
auto x = *oAlarms & static_cast<uint16_t>(JkBms::AlarmBits::x); \
162+
addLiveView##t(root, "JkBmsIssue"#x, x > 0);
163+
164+
ISSUE(Warning, LowCapacity);
165+
ISSUE(Alarm, BmsOvertemperature);
166+
ISSUE(Alarm, ChargingOvervoltage);
167+
ISSUE(Alarm, DischargeUndervoltage);
168+
ISSUE(Alarm, BatteryOvertemperature);
169+
ISSUE(Alarm, ChargingOvercurrent);
170+
ISSUE(Alarm, DischargeOvercurrent);
171+
ISSUE(Alarm, CellVoltageDifference);
172+
ISSUE(Alarm, BatteryBoxOvertemperature);
173+
ISSUE(Alarm, BatteryUndertemperature);
174+
ISSUE(Alarm, CellOvervoltage);
175+
ISSUE(Alarm, CellUndervoltage);
176+
ISSUE(Alarm, AProtect);
177+
ISSUE(Alarm, BProtect);
178+
#undef ISSUE
108179
}
109180
}
110181

@@ -174,6 +245,43 @@ void JkBmsBatteryStats::mqttPublish() const
174245
MqttSettings.publish(topic, iter->second.getValueText().c_str());
175246
}
176247

248+
auto oCellVoltages = _dataPoints.get<Label::CellsMilliVolt>();
249+
if (oCellVoltages.has_value() && (fullPublish || _cellVoltageTimestamp > _lastMqttPublish)) {
250+
unsigned idx = 1;
251+
for (auto iter = oCellVoltages->cbegin(); iter != oCellVoltages->cend(); ++iter) {
252+
String topic("battery/Cell");
253+
topic += String(idx);
254+
topic += "MilliVolt";
255+
256+
MqttSettings.publish(topic, String(iter->second));
257+
258+
++idx;
259+
}
260+
261+
MqttSettings.publish("battery/CellMinMilliVolt", String(_cellMinMilliVolt));
262+
MqttSettings.publish("battery/CellAvgMilliVolt", String(_cellAvgMilliVolt));
263+
MqttSettings.publish("battery/CellMaxMilliVolt", String(_cellMaxMilliVolt));
264+
MqttSettings.publish("battery/CellDiffMilliVolt", String(_cellMaxMilliVolt - _cellMinMilliVolt));
265+
}
266+
267+
auto oAlarms = _dataPoints.get<Label::AlarmsBitmask>();
268+
if (oAlarms.has_value()) {
269+
for (auto iter = JkBms::AlarmBitTexts.begin(); iter != JkBms::AlarmBitTexts.end(); ++iter) {
270+
auto bit = iter->first;
271+
String value = (*oAlarms & static_cast<uint16_t>(bit))?"1":"0";
272+
MqttSettings.publish(String("battery/alarms/") + iter->second.c_str(), value);
273+
}
274+
}
275+
276+
auto oStatus = _dataPoints.get<Label::StatusBitmask>();
277+
if (oStatus.has_value()) {
278+
for (auto iter = JkBms::StatusBitTexts.begin(); iter != JkBms::StatusBitTexts.end(); ++iter) {
279+
auto bit = iter->first;
280+
String value = (*oStatus & static_cast<uint16_t>(bit))?"1":"0";
281+
MqttSettings.publish(String("battery/status/") + iter->second.c_str(), value);
282+
}
283+
}
284+
177285
_lastMqttPublish = millis();
178286
if (fullPublish) { _lastFullMqttPublish = _lastMqttPublish; }
179287
}
@@ -201,6 +309,20 @@ void JkBmsBatteryStats::updateFrom(JkBms::DataPointContainer const& dp)
201309

202310
_dataPoints.updateFrom(dp);
203311

312+
auto oCellVoltages = _dataPoints.get<Label::CellsMilliVolt>();
313+
if (oCellVoltages.has_value()) {
314+
for (auto iter = oCellVoltages->cbegin(); iter != oCellVoltages->cend(); ++iter) {
315+
if (iter == oCellVoltages->cbegin()) {
316+
_cellMinMilliVolt = _cellAvgMilliVolt = _cellMaxMilliVolt = iter->second;
317+
continue;
318+
}
319+
_cellMinMilliVolt = std::min(_cellMinMilliVolt, iter->second);
320+
_cellAvgMilliVolt = (_cellAvgMilliVolt + iter->second) / 2;
321+
_cellMaxMilliVolt = std::max(_cellMaxMilliVolt, iter->second);
322+
}
323+
_cellVoltageTimestamp = millis();
324+
}
325+
204326
_lastUpdate = millis();
205327
}
206328

@@ -216,7 +338,7 @@ void VictronSmartShuntStats::updateFrom(VeDirectShuntController::veShuntStruct c
216338
_manufacturer = "Victron " + _modelName;
217339
_temperature = shuntData.T;
218340
_tempPresent = shuntData.tempPresent;
219-
341+
220342
// shuntData.AR is a bitfield, so we need to check each bit individually
221343
_alarmLowVoltage = shuntData.AR & 1;
222344
_alarmHighVoltage = shuntData.AR & 2;
@@ -233,19 +355,19 @@ void VictronSmartShuntStats::getLiveViewData(JsonVariant& root) const {
233355

234356
// values go into the "Status" card of the web application
235357
addLiveViewValue(root, "voltage", _voltage, "V", 2);
236-
addLiveViewValue(root, "current", _current, "A", 1);
358+
addLiveViewValue(root, "current", _current, "A", 1);
237359
addLiveViewValue(root, "chargeCycles", _chargeCycles, "", 0);
238360
addLiveViewValue(root, "chargedEnergy", _chargedEnergy, "KWh", 1);
239361
addLiveViewValue(root, "dischargedEnergy", _dischargedEnergy, "KWh", 1);
240362
if (_tempPresent) {
241363
addLiveViewValue(root, "temperature", _temperature, "°C", 0);
242364
}
243-
365+
244366
addLiveViewAlarm(root, "lowVoltage", _alarmLowVoltage);
245367
addLiveViewAlarm(root, "highVoltage", _alarmHighVoltage);
246368
addLiveViewAlarm(root, "lowSOC", _alarmLowSOC);
247369
addLiveViewAlarm(root, "lowTemperature", _alarmLowTemperature);
248-
addLiveViewAlarm(root, "highTemperature", _alarmHighTemperature);
370+
addLiveViewAlarm(root, "highTemperature", _alarmHighTemperature);
249371
}
250372

251373
void VictronSmartShuntStats::mqttPublish() const {

src/JkBmsController.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,84 @@ class DummySerial {
113113
0x31, 0x41, 0x32, 0x34, 0x53, 0x31, 0x35, 0x50,
114114
0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00,
115115
0x00, 0x4f, 0xc1
116+
},
117+
{
118+
0x4e, 0x57, 0x01, 0x21, 0x00, 0x00, 0x00, 0x00,
119+
0x06, 0x00, 0x01, 0x79, 0x30, 0x01, 0x0c, 0x13,
120+
0x02, 0x0c, 0x12, 0x03, 0x0c, 0x0f, 0x04, 0x0c,
121+
0x15, 0x05, 0x0c, 0x0d, 0x06, 0x0c, 0x13, 0x07,
122+
0x0c, 0x16, 0x08, 0x0c, 0x13, 0x09, 0x0b, 0xdb,
123+
0x0a, 0x0b, 0xf6, 0x0b, 0x0c, 0x17, 0x0c, 0x0b,
124+
0xf5, 0x0d, 0x0c, 0x16, 0x0e, 0x0c, 0x1a, 0x0f,
125+
0x0c, 0x1b, 0x10, 0x0c, 0x1c, 0x80, 0x00, 0x18,
126+
0x81, 0x00, 0x18, 0x82, 0x00, 0x18, 0x83, 0x13,
127+
0x49, 0x84, 0x00, 0x00, 0x85, 0x00, 0x86, 0x02,
128+
0x87, 0x00, 0x23, 0x89, 0x00, 0x00, 0x20, 0x14,
129+
0x8a, 0x00, 0x10, 0x8b, 0x00, 0x08, 0x8c, 0x00,
130+
0x05, 0x8e, 0x16, 0x80, 0x8f, 0x12, 0xc0, 0x90,
131+
0x0e, 0x10, 0x91, 0x0c, 0xda, 0x92, 0x00, 0x05,
132+
0x93, 0x0b, 0xb8, 0x94, 0x0c, 0x80, 0x95, 0x00,
133+
0x05, 0x96, 0x01, 0x2c, 0x97, 0x00, 0x28, 0x98,
134+
0x01, 0x2c, 0x99, 0x00, 0x28, 0x9a, 0x00, 0x1e,
135+
0x9b, 0x0b, 0xb8, 0x9c, 0x00, 0x0a, 0x9d, 0x01,
136+
0x9e, 0x00, 0x64, 0x9f, 0x00, 0x50, 0xa0, 0x00,
137+
0x64, 0xa1, 0x00, 0x64, 0xa2, 0x00, 0x14, 0xa3,
138+
0x00, 0x46, 0xa4, 0x00, 0x46, 0xa5, 0x00, 0x00,
139+
0xa6, 0x00, 0x02, 0xa7, 0xff, 0xec, 0xa8, 0xff,
140+
0xf6, 0xa9, 0x10, 0xaa, 0x00, 0x00, 0x00, 0xe6,
141+
0xab, 0x01, 0xac, 0x01, 0xad, 0x04, 0x4d, 0xae,
142+
0x01, 0xaf, 0x00, 0xb0, 0x00, 0x0a, 0xb1, 0x14,
143+
0xb2, 0x32, 0x32, 0x31, 0x31, 0x38, 0x37, 0x00,
144+
0x00, 0x00, 0x00, 0xb3, 0x00, 0xb4, 0x62, 0x65,
145+
0x6b, 0x69, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x32,
146+
0x33, 0x30, 0x36, 0xb6, 0x00, 0x02, 0x17, 0x10,
147+
0xb7, 0x31, 0x31, 0x2e, 0x58, 0x57, 0x5f, 0x53,
148+
0x31, 0x31, 0x2e, 0x32, 0x36, 0x32, 0x48, 0x5f,
149+
0xb8, 0x00, 0xb9, 0x00, 0x00, 0x00, 0xe6, 0xba,
150+
0x62, 0x65, 0x6b, 0x69, 0x00, 0x00, 0x00, 0x00,
151+
0x00, 0x00, 0x00, 0x00, 0x4a, 0x4b, 0x5f, 0x42,
152+
0x31, 0x41, 0x32, 0x34, 0x53, 0x31, 0x35, 0x50,
153+
0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00,
154+
0x00, 0x45, 0xce
155+
},
156+
{
157+
0x4e, 0x57, 0x01, 0x21, 0x00, 0x00, 0x00, 0x00,
158+
0x06, 0x00, 0x01, 0x79, 0x30, 0x01, 0x0c, 0x07,
159+
0x02, 0x0c, 0x0a, 0x03, 0x0c, 0x0b, 0x04, 0x0c,
160+
0x08, 0x05, 0x0c, 0x05, 0x06, 0x0c, 0x0b, 0x07,
161+
0x0c, 0x07, 0x08, 0x0c, 0x0a, 0x09, 0x0c, 0x08,
162+
0x0a, 0x0c, 0x06, 0x0b, 0x0c, 0x0a, 0x0c, 0x0c,
163+
0x05, 0x0d, 0x0c, 0x0a, 0x0e, 0x0c, 0x0a, 0x0f,
164+
0x0c, 0x0a, 0x10, 0x0c, 0x0a, 0x80, 0x00, 0x06,
165+
0x81, 0x00, 0x03, 0x82, 0x00, 0x03, 0x83, 0x13,
166+
0x40, 0x84, 0x00, 0x00, 0x85, 0x29, 0x86, 0x02,
167+
0x87, 0x00, 0x01, 0x89, 0x00, 0x00, 0x01, 0x0a,
168+
0x8a, 0x00, 0x10, 0x8b, 0x02, 0x00, 0x8c, 0x00,
169+
0x02, 0x8e, 0x16, 0x80, 0x8f, 0x10, 0x40, 0x90,
170+
0x0e, 0x10, 0x91, 0x0d, 0xde, 0x92, 0x00, 0x05,
171+
0x93, 0x0a, 0x28, 0x94, 0x0a, 0x5a, 0x95, 0x00,
172+
0x05, 0x96, 0x01, 0x2c, 0x97, 0x00, 0x28, 0x98,
173+
0x01, 0x2c, 0x99, 0x00, 0x28, 0x9a, 0x00, 0x1e,
174+
0x9b, 0x0b, 0xb8, 0x9c, 0x00, 0x0a, 0x9d, 0x01,
175+
0x9e, 0x00, 0x5a, 0x9f, 0x00, 0x50, 0xa0, 0x00,
176+
0x64, 0xa1, 0x00, 0x64, 0xa2, 0x00, 0x14, 0xa3,
177+
0x00, 0x37, 0xa4, 0x00, 0x37, 0xa5, 0x00, 0x03,
178+
0xa6, 0x00, 0x05, 0xa7, 0xff, 0xec, 0xa8, 0xff,
179+
0xf6, 0xa9, 0x10, 0xaa, 0x00, 0x00, 0x00, 0xe6,
180+
0xab, 0x01, 0xac, 0x01, 0xad, 0x04, 0x4d, 0xae,
181+
0x01, 0xaf, 0x00, 0xb0, 0x00, 0x0a, 0xb1, 0x14,
182+
0xb2, 0x32, 0x32, 0x31, 0x31, 0x38, 0x37, 0x00,
183+
0x00, 0x00, 0x00, 0xb3, 0x00, 0xb4, 0x62, 0x65,
184+
0x6b, 0x69, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x32,
185+
0x33, 0x30, 0x36, 0xb6, 0x00, 0x03, 0xb7, 0x2d,
186+
0xb7, 0x31, 0x31, 0x2e, 0x58, 0x57, 0x5f, 0x53,
187+
0x31, 0x31, 0x2e, 0x32, 0x36, 0x32, 0x48, 0x5f,
188+
0xb8, 0x00, 0xb9, 0x00, 0x00, 0x00, 0xe6, 0xba,
189+
0x62, 0x65, 0x6b, 0x69, 0x00, 0x00, 0x00, 0x00,
190+
0x00, 0x00, 0x00, 0x00, 0x4a, 0x4b, 0x5f, 0x42,
191+
0x31, 0x41, 0x32, 0x34, 0x53, 0x31, 0x35, 0x50,
192+
0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00,
193+
0x00, 0x41, 0x7b
116194
}
117195
};
118196
size_t _msg_idx = 0;

0 commit comments

Comments
 (0)