Add JBD BMS cell voltage telemetry
modified: firmware/esp32/xterra-controller/bms.cpp modified: firmware/esp32/xterra-controller/bms.h modified: firmware/esp32/xterra-controller/xterra-controller.ino
This commit is contained in:
@@ -25,6 +25,10 @@ static uint8_t JBD_STATUS_REQUEST[] = {
|
||||
0xDD, 0xA5, 0x03, 0x00, 0xFF, 0xFD, 0x77
|
||||
};
|
||||
|
||||
static uint8_t JBD_CELL_REQUEST[] = {
|
||||
0xDD, 0xA5, 0x04, 0x00, 0xFF, 0xFC, 0x77
|
||||
};
|
||||
|
||||
static uint16_t readU16(const uint8_t* data, int index) {
|
||||
return ((uint16_t)data[index] << 8) | data[index + 1];
|
||||
}
|
||||
@@ -36,6 +40,7 @@ static int16_t readS16(const uint8_t* data, int index) {
|
||||
static void resetResponseBuffer() {
|
||||
responseLength = 0;
|
||||
responseReady = false;
|
||||
memset(responseBuffer, 0, sizeof(responseBuffer));
|
||||
}
|
||||
|
||||
static void handleNotify(
|
||||
@@ -104,14 +109,37 @@ static bool connectBms() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool requestPacket(uint8_t* command, size_t commandLength) {
|
||||
resetResponseBuffer();
|
||||
|
||||
bool ok = writeChar->writeValue(command, commandLength, false);
|
||||
if (!ok) {
|
||||
Serial.println("BMS: Write failed");
|
||||
bmsData.connected = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned long start = millis();
|
||||
while (!responseReady && millis() - start < 2000) {
|
||||
delay(10);
|
||||
}
|
||||
|
||||
if (!responseReady) {
|
||||
Serial.println("BMS: Read timeout");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseBasicInfo() {
|
||||
if (responseLength < 40) {
|
||||
Serial.println("BMS: Response too short");
|
||||
Serial.println("BMS: Basic response too short");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (responseBuffer[0] != 0xDD || responseBuffer[1] != 0x03) {
|
||||
Serial.println("BMS: Unexpected response header");
|
||||
Serial.println("BMS: Unexpected basic response header");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -122,12 +150,12 @@ static bool parseBasicInfo() {
|
||||
uint16_t cyclesRaw = readU16(responseBuffer, 12);
|
||||
|
||||
uint8_t socRaw = responseBuffer[23];
|
||||
uint8_t mosTempAndCellOffset = 24;
|
||||
|
||||
uint8_t cellCount = responseBuffer[mosTempAndCellOffset + 1];
|
||||
uint8_t ntcCount = responseBuffer[mosTempAndCellOffset + 2];
|
||||
uint8_t offset = 24;
|
||||
uint8_t cellCount = responseBuffer[offset + 1];
|
||||
uint8_t ntcCount = responseBuffer[offset + 2];
|
||||
|
||||
int tempOffset = mosTempAndCellOffset + 3;
|
||||
int tempOffset = offset + 3;
|
||||
float tempF = 0;
|
||||
|
||||
if (ntcCount > 0 && responseLength > (size_t)(tempOffset + 1)) {
|
||||
@@ -159,6 +187,66 @@ static bool parseBasicInfo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseCellVoltages() {
|
||||
if (responseLength < 7) {
|
||||
Serial.println("BMS: Cell response too short");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (responseBuffer[0] != 0xDD || responseBuffer[1] != 0x04) {
|
||||
Serial.println("BMS: Unexpected cell response header");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t payloadLength = responseBuffer[3];
|
||||
int cellsReported = payloadLength / 2;
|
||||
|
||||
if (cellsReported > BMS_MAX_CELLS) {
|
||||
cellsReported = BMS_MAX_CELLS;
|
||||
}
|
||||
|
||||
if (cellsReported <= 0) {
|
||||
Serial.println("BMS: No cell voltages reported");
|
||||
return false;
|
||||
}
|
||||
|
||||
float minV = 99.0;
|
||||
float maxV = 0.0;
|
||||
|
||||
for (int i = 0; i < cellsReported; i++) {
|
||||
uint16_t mv = readU16(responseBuffer, 4 + (i * 2));
|
||||
float volts = mv / 1000.0;
|
||||
|
||||
bmsData.cellVoltages[i] = volts;
|
||||
|
||||
if (volts < minV) {
|
||||
minV = volts;
|
||||
}
|
||||
|
||||
if (volts > maxV) {
|
||||
maxV = volts;
|
||||
}
|
||||
}
|
||||
|
||||
bmsData.cellCount = cellsReported;
|
||||
bmsData.cellMinVoltage = minV;
|
||||
bmsData.cellMaxVoltage = maxV;
|
||||
bmsData.cellDeltaMv = (int)((maxV - minV) * 1000.0 + 0.5);
|
||||
bmsData.cellsValid = true;
|
||||
|
||||
Serial.print("BMS: Cells ");
|
||||
Serial.print(cellsReported);
|
||||
Serial.print(", min ");
|
||||
Serial.print(minV, 3);
|
||||
Serial.print("V, max ");
|
||||
Serial.print(maxV, 3);
|
||||
Serial.print("V, delta ");
|
||||
Serial.print(bmsData.cellDeltaMv);
|
||||
Serial.println("mV");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void initBms() {
|
||||
NimBLEDevice::init("XterraESP32");
|
||||
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
||||
@@ -176,29 +264,13 @@ void updateBms() {
|
||||
return;
|
||||
}
|
||||
|
||||
resetResponseBuffer();
|
||||
|
||||
bool ok = writeChar->writeValue(
|
||||
JBD_STATUS_REQUEST,
|
||||
sizeof(JBD_STATUS_REQUEST),
|
||||
false
|
||||
);
|
||||
|
||||
if (!ok) {
|
||||
Serial.println("BMS: Write failed");
|
||||
bmsData.connected = false;
|
||||
return;
|
||||
if (requestPacket(JBD_STATUS_REQUEST, sizeof(JBD_STATUS_REQUEST))) {
|
||||
parseBasicInfo();
|
||||
}
|
||||
|
||||
unsigned long start = millis();
|
||||
while (!responseReady && millis() - start < 2000) {
|
||||
delay(10);
|
||||
}
|
||||
delay(100);
|
||||
|
||||
if (!responseReady) {
|
||||
Serial.println("BMS: Read timeout");
|
||||
return;
|
||||
if (requestPacket(JBD_CELL_REQUEST, sizeof(JBD_CELL_REQUEST))) {
|
||||
parseCellVoltages();
|
||||
}
|
||||
|
||||
parseBasicInfo();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#define BMS_MAX_CELLS 8
|
||||
|
||||
struct BmsData {
|
||||
bool connected = false;
|
||||
bool valid = false;
|
||||
bool cellsValid = false;
|
||||
|
||||
float soc = 0;
|
||||
float voltage = 0;
|
||||
@@ -14,6 +17,11 @@ struct BmsData {
|
||||
int cycleCount = 0;
|
||||
int cellCount = 0;
|
||||
int ntcCount = 0;
|
||||
|
||||
float cellVoltages[BMS_MAX_CELLS] = {0};
|
||||
float cellMinVoltage = 0;
|
||||
float cellMaxVoltage = 0;
|
||||
int cellDeltaMv = 0;
|
||||
};
|
||||
|
||||
extern BmsData bmsData;
|
||||
|
||||
@@ -45,6 +45,17 @@ void buildStatusDocument(JsonDocument& doc) {
|
||||
battery["cycle_count"] = bmsData.cycleCount;
|
||||
battery["cell_count"] = bmsData.cellCount;
|
||||
battery["ntc_count"] = bmsData.ntcCount;
|
||||
|
||||
JsonArray cells = battery.createNestedArray("cell_voltages");
|
||||
if (bmsData.cellsValid) {
|
||||
for (int i = 0; i < bmsData.cellCount; i++) {
|
||||
cells.add(bmsData.cellVoltages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
battery["cell_min_voltage"] = bmsData.cellMinVoltage;
|
||||
battery["cell_max_voltage"] = bmsData.cellMaxVoltage;
|
||||
battery["cell_delta_mv"] = bmsData.cellDeltaMv;
|
||||
} else {
|
||||
battery["source"] = "placeholder";
|
||||
battery["connected"] = false;
|
||||
|
||||
Reference in New Issue
Block a user