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
|
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) {
|
static uint16_t readU16(const uint8_t* data, int index) {
|
||||||
return ((uint16_t)data[index] << 8) | data[index + 1];
|
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() {
|
static void resetResponseBuffer() {
|
||||||
responseLength = 0;
|
responseLength = 0;
|
||||||
responseReady = false;
|
responseReady = false;
|
||||||
|
memset(responseBuffer, 0, sizeof(responseBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleNotify(
|
static void handleNotify(
|
||||||
@@ -104,14 +109,37 @@ static bool connectBms() {
|
|||||||
return true;
|
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() {
|
static bool parseBasicInfo() {
|
||||||
if (responseLength < 40) {
|
if (responseLength < 40) {
|
||||||
Serial.println("BMS: Response too short");
|
Serial.println("BMS: Basic response too short");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseBuffer[0] != 0xDD || responseBuffer[1] != 0x03) {
|
if (responseBuffer[0] != 0xDD || responseBuffer[1] != 0x03) {
|
||||||
Serial.println("BMS: Unexpected response header");
|
Serial.println("BMS: Unexpected basic response header");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,12 +150,12 @@ static bool parseBasicInfo() {
|
|||||||
uint16_t cyclesRaw = readU16(responseBuffer, 12);
|
uint16_t cyclesRaw = readU16(responseBuffer, 12);
|
||||||
|
|
||||||
uint8_t socRaw = responseBuffer[23];
|
uint8_t socRaw = responseBuffer[23];
|
||||||
uint8_t mosTempAndCellOffset = 24;
|
|
||||||
|
|
||||||
uint8_t cellCount = responseBuffer[mosTempAndCellOffset + 1];
|
uint8_t offset = 24;
|
||||||
uint8_t ntcCount = responseBuffer[mosTempAndCellOffset + 2];
|
uint8_t cellCount = responseBuffer[offset + 1];
|
||||||
|
uint8_t ntcCount = responseBuffer[offset + 2];
|
||||||
|
|
||||||
int tempOffset = mosTempAndCellOffset + 3;
|
int tempOffset = offset + 3;
|
||||||
float tempF = 0;
|
float tempF = 0;
|
||||||
|
|
||||||
if (ntcCount > 0 && responseLength > (size_t)(tempOffset + 1)) {
|
if (ntcCount > 0 && responseLength > (size_t)(tempOffset + 1)) {
|
||||||
@@ -159,6 +187,66 @@ static bool parseBasicInfo() {
|
|||||||
return true;
|
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() {
|
void initBms() {
|
||||||
NimBLEDevice::init("XterraESP32");
|
NimBLEDevice::init("XterraESP32");
|
||||||
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
||||||
@@ -176,29 +264,13 @@ void updateBms() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetResponseBuffer();
|
if (requestPacket(JBD_STATUS_REQUEST, sizeof(JBD_STATUS_REQUEST))) {
|
||||||
|
parseBasicInfo();
|
||||||
bool ok = writeChar->writeValue(
|
|
||||||
JBD_STATUS_REQUEST,
|
|
||||||
sizeof(JBD_STATUS_REQUEST),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
Serial.println("BMS: Write failed");
|
|
||||||
bmsData.connected = false;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long start = millis();
|
delay(100);
|
||||||
while (!responseReady && millis() - start < 2000) {
|
|
||||||
delay(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!responseReady) {
|
if (requestPacket(JBD_CELL_REQUEST, sizeof(JBD_CELL_REQUEST))) {
|
||||||
Serial.println("BMS: Read timeout");
|
parseCellVoltages();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parseBasicInfo();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#define BMS_MAX_CELLS 8
|
||||||
|
|
||||||
struct BmsData {
|
struct BmsData {
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
|
bool cellsValid = false;
|
||||||
|
|
||||||
float soc = 0;
|
float soc = 0;
|
||||||
float voltage = 0;
|
float voltage = 0;
|
||||||
@@ -14,6 +17,11 @@ struct BmsData {
|
|||||||
int cycleCount = 0;
|
int cycleCount = 0;
|
||||||
int cellCount = 0;
|
int cellCount = 0;
|
||||||
int ntcCount = 0;
|
int ntcCount = 0;
|
||||||
|
|
||||||
|
float cellVoltages[BMS_MAX_CELLS] = {0};
|
||||||
|
float cellMinVoltage = 0;
|
||||||
|
float cellMaxVoltage = 0;
|
||||||
|
int cellDeltaMv = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern BmsData bmsData;
|
extern BmsData bmsData;
|
||||||
|
|||||||
@@ -45,6 +45,17 @@ void buildStatusDocument(JsonDocument& doc) {
|
|||||||
battery["cycle_count"] = bmsData.cycleCount;
|
battery["cycle_count"] = bmsData.cycleCount;
|
||||||
battery["cell_count"] = bmsData.cellCount;
|
battery["cell_count"] = bmsData.cellCount;
|
||||||
battery["ntc_count"] = bmsData.ntcCount;
|
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 {
|
} else {
|
||||||
battery["source"] = "placeholder";
|
battery["source"] = "placeholder";
|
||||||
battery["connected"] = false;
|
battery["connected"] = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user