mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 18:29:48 +02:00
Merge branch 'main' into bugfix/nissan-remaining-capacity
This commit is contained in:
commit
ca7914df9d
7 changed files with 165 additions and 23 deletions
|
@ -10,7 +10,7 @@ ci:
|
|||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v20.1.3
|
||||
rev: v20.1.5
|
||||
hooks:
|
||||
- id: clang-format
|
||||
args: [-Werror] # change formatting warnings to errors, hook includes -i (Inplace edit) by default
|
||||
|
|
|
@ -449,6 +449,36 @@ void NissanLeafBattery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
if (group_7bb == 0x06) //Balancing resistor status
|
||||
{
|
||||
if (rx_frame.data.u8[0] == 0x10) { //First frame (10 1A 61 06 [14 55 55 51])
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// Byte 4 - 7 (bits 0-31)
|
||||
for (int byte_i = 0; byte_i < 4; byte_i++) {
|
||||
battery_balancing_shunts[byte_i * 8 + i] = (rx_frame.data.u8[4 + byte_i] & (1 << i)) >> i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rx_frame.data.u8[0] == 0x21) { // Second frame (21 [50 55 41 2B 56 54 15])
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// Byte 1 to 7 (bits 32-87)
|
||||
for (int byte_i = 0; byte_i < 7; byte_i++) {
|
||||
battery_balancing_shunts[32 + byte_i * 8 + i] = (rx_frame.data.u8[1 + byte_i] & (1 << i)) >> i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rx_frame.data.u8[0] == 0x22) { //Third frame (22 51 FF FF FF FF FF FF)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// Byte 1 (bits 88-95)
|
||||
battery_balancing_shunts[88 + i] = (rx_frame.data.u8[1] & (1 << i)) >> i;
|
||||
}
|
||||
memcpy(datalayer_battery->status.cell_balancing_status, battery_balancing_shunts, 96 * sizeof(bool));
|
||||
}
|
||||
|
||||
if (rx_frame.data.u8[0] == 0x23) { //Fourth frame (23 FF FF FF FF FF FF FF)
|
||||
}
|
||||
}
|
||||
|
||||
if (group_7bb == 0x83) //BatteryPartNumber
|
||||
{
|
||||
if (rx_frame.data.u8[0] == 0x10) { //First frame (101A6183334E4B32)
|
||||
|
@ -700,7 +730,7 @@ void NissanLeafBattery::transmit_can(unsigned long currentMillis) {
|
|||
if (!stop_battery_query) {
|
||||
|
||||
// Move to the next group
|
||||
PIDindex = (PIDindex + 1) % 6; // 6 = amount of elements in the PIDgroups[]
|
||||
PIDindex = (PIDindex + 1) % 7; // 7 = amount of elements in the PIDgroups[]
|
||||
LEAF_GROUP_REQUEST.data.u8[2] = PIDgroups[PIDindex];
|
||||
|
||||
transmit_can_frame(&LEAF_GROUP_REQUEST, can_interface);
|
||||
|
|
|
@ -82,7 +82,7 @@ class NissanLeafBattery : public CanBattery {
|
|||
.ID = 0x1D4,
|
||||
.data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}};
|
||||
// Active polling messages
|
||||
uint8_t PIDgroups[6] = {0x01, 0x02, 0x04, 0x83, 0x84, 0x90};
|
||||
uint8_t PIDgroups[7] = {0x01, 0x02, 0x04, 0x06, 0x83, 0x84, 0x90};
|
||||
uint8_t PIDindex = 0;
|
||||
CAN_frame LEAF_GROUP_REQUEST = {.FD = false,
|
||||
.ext_ID = false,
|
||||
|
@ -155,7 +155,8 @@ class NissanLeafBattery : public CanBattery {
|
|||
uint8_t group_7bb = 0;
|
||||
bool stop_battery_query = true;
|
||||
uint8_t hold_off_with_polling_10seconds = 2; //Paused for 20 seconds on startup
|
||||
uint16_t battery_cell_voltages[97]; //array with all the cellvoltages
|
||||
uint16_t battery_cell_voltages[96]; //array with all the cellvoltages
|
||||
bool battery_balancing_shunts[96]; //array with all the balancing resistors
|
||||
uint8_t battery_cellcounter = 0;
|
||||
uint16_t battery_min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
uint16_t battery_HX = 0; //Internal resistance
|
||||
|
|
|
@ -108,8 +108,16 @@ void RenaultZoeGen2Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
switch (rx_frame.ID) {
|
||||
case 0x18DAF1DB: // LBC Reply from active polling
|
||||
//frame 2 & 3 contains
|
||||
reply_poll = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
|
||||
|
||||
if (rx_frame.data.u8[0] == 0x10) { //First frame of a group
|
||||
transmit_can_frame(&ZOE_POLL_FLOW_CONTROL, can_config.battery);
|
||||
//frame 2 & 3 contains which PID is sent
|
||||
reply_poll = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
|
||||
}
|
||||
|
||||
if (rx_frame.data.u8[0] < 0x10) { //One line responses
|
||||
reply_poll = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
|
||||
}
|
||||
|
||||
switch (reply_poll) {
|
||||
case POLL_SOC:
|
||||
|
@ -200,6 +208,29 @@ void RenaultZoeGen2Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
|
|||
battery_bms_state = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||
break;
|
||||
case POLL_BALANCE_SWITCHES:
|
||||
if (rx_frame.data.u8[0] == 0x10) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// Byte 4 - 7 (bits 0-31)
|
||||
for (int byte_i = 0; byte_i < 4; byte_i++) {
|
||||
battery_balancing_shunts[byte_i * 8 + i] = (rx_frame.data.u8[4 + byte_i] & (1 << i)) >> i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rx_frame.data.u8[0] == 0x21) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// Byte 1 to 7 (bits 32-87)
|
||||
for (int byte_i = 0; byte_i < 7; byte_i++) {
|
||||
battery_balancing_shunts[32 + byte_i * 8 + i] = (rx_frame.data.u8[1 + byte_i] & (1 << i)) >> i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rx_frame.data.u8[0] == 0x22) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// Byte 1 (bits 88-95)
|
||||
battery_balancing_shunts[88 + i] = (rx_frame.data.u8[1] & (1 << i)) >> i;
|
||||
}
|
||||
memcpy(datalayer.battery.status.cell_balancing_status, battery_balancing_shunts, 96 * sizeof(bool));
|
||||
}
|
||||
battery_balance_switches = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||
break;
|
||||
case POLL_ENERGY_COMPLETE:
|
||||
|
|
|
@ -205,6 +205,7 @@ class RenaultZoeGen2Battery : public CanBattery {
|
|||
uint32_t ZOE_376_time_now_s = 1745452800; // Initialized to make the battery think it is April 24, 2025
|
||||
unsigned long kProductionTimestamp_s =
|
||||
1614454107; // Production timestamp in seconds since January 1, 1970. Production timestamp used: February 25, 2021 at 8:08:27 AM GMT
|
||||
bool battery_balancing_shunts[96];
|
||||
|
||||
CAN_frame ZOE_373 = {
|
||||
.FD = false,
|
||||
|
@ -225,6 +226,11 @@ class RenaultZoeGen2Battery : public CanBattery {
|
|||
.DLC = 8,
|
||||
.ID = 0x18DADBF1,
|
||||
.data = {0x03, 0x22, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame ZOE_POLL_FLOW_CONTROL = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x18DADBF1,
|
||||
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
//NVROL Reset
|
||||
CAN_frame ZOE_NVROL_1_18DADBF1 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
|
|
|
@ -81,6 +81,11 @@ typedef struct {
|
|||
* Use with battery.info.number_of_cells to get valid data.
|
||||
*/
|
||||
uint16_t cell_voltages_mV[MAX_AMOUNT_CELLS];
|
||||
/** All balancing resistors status inside the pack, either on(1) or off(0).
|
||||
* Use with battery.info.number_of_cells to get valid data.
|
||||
* Not available for all battery manufacturers.
|
||||
*/
|
||||
bool cell_balancing_status[MAX_AMOUNT_CELLS];
|
||||
/** The "real" SOC reported from the battery, in integer-percent x 100. 9550 = 95.50% */
|
||||
uint16_t real_soc;
|
||||
/** The SOC reported to the inverter, in integer-percent x 100. 9550 = 95.50%.
|
||||
|
|
|
@ -46,6 +46,24 @@ String cellmonitor_processor(const String& var) {
|
|||
content += "<div id='graph'></div>";
|
||||
// Display single hovered value
|
||||
content += "<div id='valueDisplay'>Value: ...</div>";
|
||||
//Legend for graph
|
||||
content +=
|
||||
"<span style='color: white; background-color: blue; font-weight: bold; padding: 2px 8px; border-radius: 4px; "
|
||||
"margin-right: 15px;'>Idle</span>";
|
||||
bool battery_balancing = false;
|
||||
for (uint8_t i = 0u; i < datalayer.battery.info.number_of_cells; i++) {
|
||||
battery_balancing = datalayer.battery.status.cell_balancing_status[i];
|
||||
if (battery_balancing)
|
||||
break;
|
||||
}
|
||||
if (battery_balancing) {
|
||||
content +=
|
||||
"<span style='color: black; background-color: #00FFFF; font-weight: bold; padding: 2px 8px; border-radius: "
|
||||
"4px; margin-right: 15px;'>Balancing</span>";
|
||||
}
|
||||
content +=
|
||||
"<span style='color: white; background-color: red; font-weight: bold; padding: 2px 8px; border-radius: "
|
||||
"4px;'>Min/Max</span>";
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
@ -62,6 +80,25 @@ String cellmonitor_processor(const String& var) {
|
|||
content += "<div id='graph2'></div>";
|
||||
// Display single hovered value
|
||||
content += "<div id='valueDisplay2'>Value: ...</div>";
|
||||
//Legend for graph
|
||||
content +=
|
||||
"<span style='color: white; background-color: blue; font-weight: bold; padding: 2px 8px; border-radius: 4px; "
|
||||
"margin-right: 15px;'>Idle</span>";
|
||||
|
||||
bool battery2_balancing = false;
|
||||
for (uint8_t i = 0u; i < datalayer.battery2.info.number_of_cells; i++) {
|
||||
battery2_balancing = datalayer.battery2.status.cell_balancing_status[i];
|
||||
if (battery2_balancing)
|
||||
break;
|
||||
}
|
||||
if (battery2_balancing) {
|
||||
content +=
|
||||
"<span style='color: black; background-color: #00FFFF; font-weight: bold; padding: 2px 8px; border-radius: "
|
||||
"4px; margin-right: 15px;'>Balancing</span>";
|
||||
}
|
||||
content +=
|
||||
"<span style='color: white; background-color: red; font-weight: bold; padding: 2px 8px; border-radius: "
|
||||
"4px;'>Min/Max</span>";
|
||||
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
@ -80,6 +117,15 @@ String cellmonitor_processor(const String& var) {
|
|||
}
|
||||
content += "];";
|
||||
|
||||
content += "const balancing = [";
|
||||
for (uint8_t i = 0u; i < datalayer.battery.info.number_of_cells; i++) {
|
||||
if (datalayer.battery.status.cell_voltages_mV[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
content += datalayer.battery.status.cell_balancing_status[i] ? "true," : "false,";
|
||||
}
|
||||
content += "];";
|
||||
|
||||
content += "const min_mv = Math.min(...data) - 20;";
|
||||
content += "const max_mv = Math.max(...data) + 20;";
|
||||
content += "const min_index = data.indexOf(Math.min(...data));";
|
||||
|
@ -110,20 +156,27 @@ String cellmonitor_processor(const String& var) {
|
|||
"bar.id = `barIndex${index}`;"
|
||||
"bar.style.height = `${mV_limited}px`;"
|
||||
"bar.style.width = `${750/data.length}px`;"
|
||||
"if (balancing[index]) {"
|
||||
" bar.style.backgroundColor = '#00FFFF';" // Cyan color for balancing
|
||||
" bar.style.borderColor = '#00FFFF';"
|
||||
"} else {"
|
||||
" bar.style.backgroundColor = 'blue';" // Normal blue for non-balancing
|
||||
" bar.style.borderColor = 'white';"
|
||||
"}"
|
||||
|
||||
"const cell = document.getElementById(`cellIndex${index}`);"
|
||||
|
||||
"checkMinMax(cell, bar, index);"
|
||||
|
||||
"bar.addEventListener('mouseenter', () => {"
|
||||
"valueDisplay.textContent = `Value: ${mV}`;"
|
||||
"bar.style.backgroundColor = `lightblue`;"
|
||||
"cell.style.backgroundColor = `blue`;"
|
||||
" valueDisplay.textContent = `Value: ${mV}` + (balancing[index] ? ' (balancing)' : '');"
|
||||
" bar.style.backgroundColor = balancing[index] ? '#80FFFF' : 'lightblue';"
|
||||
" cell.style.backgroundColor = balancing[index] ? '#006666' : 'blue';"
|
||||
"});"
|
||||
|
||||
"bar.addEventListener('mouseleave', () => {"
|
||||
"valueDisplay.textContent = 'Value: ...';"
|
||||
"bar.style.backgroundColor = `blue`;"
|
||||
"bar.style.backgroundColor = balancing[index] ? '#00FFFF' : 'blue';" // Restore cyan if balancing, else blue
|
||||
"cell.style.removeProperty('background-color');"
|
||||
"});"
|
||||
|
||||
|
@ -140,20 +193,20 @@ String cellmonitor_processor(const String& var) {
|
|||
"cell.id = `cellIndex${index}`;"
|
||||
"let cellContent = `Cell ${index + 1}<br>${mV} mV`;"
|
||||
"if (mV < 3000) {"
|
||||
"cellContent = `<span class='low-voltage'>${cellContent}</span>`;"
|
||||
" cellContent = `<span class='low-voltage'>${cellContent}</span>`;"
|
||||
"}"
|
||||
"cell.innerHTML = cellContent;"
|
||||
|
||||
"cell.addEventListener('mouseenter', () => {"
|
||||
"let bar = document.getElementById(`barIndex${index}`);"
|
||||
"valueDisplay.textContent = `Value: ${mV}`;"
|
||||
"bar.style.backgroundColor = `lightblue`;"
|
||||
"cell.style.backgroundColor = `blue`;"
|
||||
"bar.style.backgroundColor = balancing[index] ? '#80FFFF' : 'lightblue';" // Lighter cyan if balancing
|
||||
"cell.style.backgroundColor = balancing[index] ? '#006666' : 'blue';" // Darker cyan if balancing
|
||||
"});"
|
||||
|
||||
"cell.addEventListener('mouseleave', () => {"
|
||||
"let bar = document.getElementById(`barIndex${index}`);"
|
||||
"bar.style.backgroundColor = `blue`;"
|
||||
"bar.style.backgroundColor = balancing[index] ? '#00FFFF' : 'blue';" // Restore original color
|
||||
"cell.style.removeProperty('background-color');"
|
||||
"});"
|
||||
|
||||
|
@ -195,6 +248,15 @@ String cellmonitor_processor(const String& var) {
|
|||
}
|
||||
content += "];";
|
||||
|
||||
content += "const balancing2 = [";
|
||||
for (uint8_t i = 0u; i < datalayer.battery2.info.number_of_cells; i++) {
|
||||
if (datalayer.battery2.status.cell_voltages_mV[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
content += datalayer.battery2.status.cell_balancing_status[i] ? "true," : "false,";
|
||||
}
|
||||
content += "];";
|
||||
|
||||
content += "const min_mv2 = Math.min(...data2) - 20;";
|
||||
content += "const max_mv2 = Math.max(...data2) + 20;";
|
||||
content += "const min_index2 = data2.indexOf(Math.min(...data2));";
|
||||
|
@ -223,20 +285,26 @@ String cellmonitor_processor(const String& var) {
|
|||
"bar2.id = `barIndex2${index2}`;"
|
||||
"bar2.style.height = `${mV_limited2}px`;"
|
||||
"bar2.style.width = `${750/data2.length}px`;"
|
||||
|
||||
"if (balancing2[index2]) {"
|
||||
" bar2.style.backgroundColor = '#00FFFF';" // Cyan color for balancing
|
||||
" bar2.style.borderColor = '#00FFFF';"
|
||||
"} else {"
|
||||
" bar2.style.backgroundColor = 'blue';" // Normal blue for non-balancing
|
||||
" bar2.style.borderColor = 'white';"
|
||||
"}"
|
||||
"const cell2 = document.getElementById(`cellIndex2${index2}`);"
|
||||
|
||||
"checkMinMax2(cell2, bar2, index2);"
|
||||
|
||||
"bar2.addEventListener('mouseenter', () => {"
|
||||
"valueDisplay2.textContent = `Value: ${mV}`;"
|
||||
"bar2.style.backgroundColor = `lightblue`;"
|
||||
"cell2.style.backgroundColor = `blue`;"
|
||||
" valueDisplay2.textContent = `Value: ${mV}` + (balancing[index2] ? ' (balancing)' : '');"
|
||||
" bar2.style.backgroundColor = balancing2[index2] ? '#80FFFF' : 'lightblue';"
|
||||
" cell2.style.backgroundColor = balancing2[index2] ? '#006666' : 'blue';"
|
||||
"});"
|
||||
|
||||
"bar2.addEventListener('mouseleave', () => {"
|
||||
"valueDisplay2.textContent = 'Value: ...';"
|
||||
"bar2.style.backgroundColor = `blue`;"
|
||||
"bar2.style.backgroundColor = balancing2[index2] ? '#00FFFF' : 'blue';" // Restore cyan if balancing, else blue
|
||||
"cell2.style.removeProperty('background-color');"
|
||||
"});"
|
||||
|
||||
|
@ -260,13 +328,13 @@ String cellmonitor_processor(const String& var) {
|
|||
"cell2.addEventListener('mouseenter', () => {"
|
||||
"let bar2 = document.getElementById(`barIndex2${index2}`);"
|
||||
"valueDisplay2.textContent = `Value: ${mV}`;"
|
||||
"bar2.style.backgroundColor = `lightblue`;"
|
||||
"cell2.style.backgroundColor = `blue`;"
|
||||
"bar2.style.backgroundColor = balancing2[index2] ? '#80FFFF' : 'lightblue';" // Lighter cyan if balancing
|
||||
"cell2.style.backgroundColor = balancing2[index2] ? '#006666' : 'blue';" // Darker cyan if balancing
|
||||
"});"
|
||||
|
||||
"cell2.addEventListener('mouseleave', () => {"
|
||||
"let bar2 = document.getElementById(`barIndex2${index2}`);"
|
||||
"bar2.style.backgroundColor = `blue`;"
|
||||
"bar2.style.backgroundColor = balancing2[index2] ? '#00FFFF' : 'blue';" // Restore original color
|
||||
"cell2.style.removeProperty('background-color');"
|
||||
"});"
|
||||
|
||||
|
@ -281,7 +349,8 @@ String cellmonitor_processor(const String& var) {
|
|||
"const max_mv2 = Math.max(...data2);"
|
||||
"const cell_dev2 = max_mv2 - min_mv2;"
|
||||
"const voltVal2 = document.getElementById('voltageValues2');"
|
||||
"voltVal2.innerHTML = `Max Voltage : ${max_mv2} mV<br>Min Voltage: ${min_mv2} mV<br>Voltage Deviation: "
|
||||
"voltVal2.innerHTML = `Battery #2<br>Max Voltage : ${max_mv2} mV<br>Min Voltage: ${min_mv2} mV<br>Voltage "
|
||||
"Deviation: "
|
||||
"${cell_dev2} mV`"
|
||||
"}";
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue