From 8444855f24822dc5d1fcea0ece291a4638e307dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 17 Aug 2025 20:02:11 +0300 Subject: [PATCH 01/15] Add new box for contactor status --- Software/src/devboard/webserver/webserver.cpp | 166 ++++++++++-------- 1 file changed, 94 insertions(+), 72 deletions(-) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 174efc78..3d4b6975 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -824,6 +824,28 @@ String processor(const String& var) { content += "button:hover { background-color: #3A4A52; }"; content += "h2 { font-size: 1.2em; margin: 0.3em 0 0.5em 0; }"; content += "h4 { margin: 0.6em 0; line-height: 1.2; }"; + //content += ".tooltip { position: relative; display: inline-block; }"; + content += ".tooltip .tooltiptext {"; + content += " visibility: hidden;"; + content += " width: 200px;"; + content += " background-color: #3A4A52;"; // Matching your button hover color + content += " color: white;"; + content += " text-align: center;"; + content += " border-radius: 6px;"; + content += " padding: 8px;"; + content += " position: absolute;"; + content += " z-index: 1;"; + content += " bottom: 125%;"; + content += " left: 50%;"; + content += " margin-left: -100px;"; + content += " opacity: 0;"; + content += " transition: opacity 0.3s;"; + content += " font-size: 0.9em;"; + content += " font-weight: normal;"; + content += " line-height: 1.4;"; + content += "}"; + content += ".tooltip:hover .tooltiptext { visibility: visible; opacity: 1; }"; + content += ".tooltip-icon { color: #505E67; cursor: help; }"; // Matching your button color content += ""; // Compact header @@ -1103,58 +1125,6 @@ String processor(const String& var) { } } - content += "

Battery allows contactor closing: "; - if (datalayer.system.status.battery_allows_contactor_closing == true) { - content += ""; - } else { - content += ""; - } - - content += " Inverter allows contactor closing: "; - if (datalayer.system.status.inverter_allows_contactor_closing == true) { - content += "

"; - } else { - content += ""; - } - if (emulator_pause_status == NORMAL) - content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; - else - content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; - - if (contactor_control_enabled) { - content += "

Contactors controlled by emulator, state: "; - if (datalayer.system.status.contactors_engaged) { - content += "ON"; - } else { - content += "OFF"; - } - content += "

"; - if (contactor_control_enabled_double_battery) { - if (pwm_contactor_control) { - content += "

Cont. Neg.: "; - if (datalayer.system.status.contactors_battery2_engaged) { - content += "Economized"; - content += " Cont. Pos.: "; - content += "Economized"; - } else { - content += ""; - content += " Cont. Pos.: "; - content += ""; - } - } else if ( - esp32hal->SECOND_BATTERY_CONTACTORS_PIN() != - GPIO_NUM_NC) { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded - content += "

Cont. Neg.: "; - if (digitalRead(esp32hal->SECOND_BATTERY_CONTACTORS_PIN()) == HIGH) { - content += ""; - } else { - content += ""; - } - } //no PWM_CONTACTOR_CONTROL - content += "

"; - } - } - // Close the block content += ""; @@ -1253,26 +1223,6 @@ String processor(const String& var) { content += "

Battery charging!

"; } - content += "

Automatic contactor closing allowed:

"; - content += "

Battery: "; - if (datalayer.system.status.battery2_allowed_contactor_closing == true) { - content += ""; - } else { - content += ""; - } - - content += " Inverter: "; - if (datalayer.system.status.inverter_allows_contactor_closing == true) { - content += "

"; - } else { - content += ""; - } - - if (emulator_pause_status == NORMAL) - content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; - else - content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; - if (contactor_control_enabled) { content += "

Contactors controlled by emulator, state: "; if (datalayer.system.status.contactors_battery2_engaged) { @@ -1317,6 +1267,78 @@ String processor(const String& var) { content += ""; } } + // Block for Contactor status and component request status + // Start a new block with gray background color + content += "
"; + + if (emulator_pause_status == NORMAL) { + content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; + } else { + content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; + } + + content += "

Battery allows contactor closing: "; + if (datalayer.battery.status.bms_status == FAULT) { + content += ""; + } else { + content += ""; + } + content += " Inverter allows contactor closing: "; + if (datalayer.system.status.inverter_allows_contactor_closing == true) { + content += "

"; + } else { + content += "

"; + } + content += "

Secondary battery allowed to join "; + if (datalayer.system.status.battery2_allowed_contactor_closing == true) { + content += ""; + } else { + content += "✕ (voltage mismatch)"; + } + + if (!contactor_control_enabled) { + content += "
"; + content += "

Contactors not fully controlled via emulator [?]

"; + content += + "This means you are either running CAN controlled contactors OR manually " + "powering the contactors. Battery-Emulator will have limited amount of control over the contactors!"; + content += "
"; + } else { //contactor_control_enabled TRUE + content += "

Contactors controlled by emulator, state: "; + if (datalayer.system.status.contactors_engaged) { + content += "ON"; + } else { + content += "OFF"; + } + content += "

"; + if (contactor_control_enabled_double_battery) { + if (pwm_contactor_control) { + content += "

Cont. Neg.: "; + if (datalayer.system.status.contactors_battery2_engaged) { + content += "Economized"; + content += " Cont. Pos.: "; + content += "Economized"; + } else { + content += ""; + content += " Cont. Pos.: "; + content += ""; + } + } else if ( + esp32hal->SECOND_BATTERY_CONTACTORS_PIN() != + GPIO_NUM_NC) { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded + content += "

Cont. Neg.: "; + if (digitalRead(esp32hal->SECOND_BATTERY_CONTACTORS_PIN()) == HIGH) { + content += ""; + } else { + content += ""; + } + } //no PWM_CONTACTOR_CONTROL + content += "

"; + } + } + + // Close the block + content += ""; if (charger) { // Start a new block with orange background color From 1645c5b337abef159fdc25c8c8969f93b097cb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 17 Aug 2025 21:24:55 +0300 Subject: [PATCH 02/15] Further improve dialogue boxes and texts --- .../comm_contactorcontrol.cpp | 9 +++--- Software/src/datalayer/datalayer.h | 4 +-- Software/src/devboard/utils/events.cpp | 4 +-- Software/src/devboard/webserver/webserver.cpp | 28 ++++++++++++------- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp index ec654d6e..c63dd4ae 100644 --- a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp +++ b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp @@ -192,7 +192,7 @@ void handle_contactors() { set(negPin, OFF, PWM_OFF_DUTY); set(posPin, OFF, PWM_OFF_DUTY); set_event(EVENT_ERROR_OPEN_CONTACTOR, 0); - datalayer.system.status.contactors_engaged = false; + datalayer.system.status.contactors_engaged = 2; return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured) } @@ -201,10 +201,9 @@ void handle_contactors() { set(prechargePin, OFF); set(negPin, OFF, PWM_OFF_DUTY); set(posPin, OFF, PWM_OFF_DUTY); - datalayer.system.status.contactors_engaged = false; + datalayer.system.status.contactors_engaged = 0; - if (datalayer.system.status.battery_allows_contactor_closing && - datalayer.system.status.inverter_allows_contactor_closing && + if (datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.settings.equipment_stop_active) { contactorStatus = START_PRECHARGE; } @@ -263,7 +262,7 @@ void handle_contactors() { set(posPin, ON, PWM_HOLD_DUTY); dbg_contactors("PRECHARGE_OFF"); contactorStatus = COMPLETED; - datalayer.system.status.contactors_engaged = true; + datalayer.system.status.contactors_engaged = 1; } break; default: diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index aca1668c..ad910865 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -310,8 +310,8 @@ struct DATALAYER_SYSTEM_STATUS_TYPE { /** True if the inverter allows for the contactors to close */ bool inverter_allows_contactor_closing = true; - /** True if the contactor controlled by battery-emulator is closed */ - bool contactors_engaged = false; + /** 0 if starting up, 1 if contactors engaged, 2 if the contactors controlled by battery-emulator is opened */ + uint8_t contactors_engaged = 0; /** True if the contactor controlled by battery-emulator is closed. Determined by check_interconnect_available(); if voltage is OK */ bool contactors_battery2_engaged = false; diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index b5ba4a8f..0f94f079 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -270,8 +270,8 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) { case EVENT_INTERFACE_MISSING: return "Configuration trying to use CAN interface not baked into the software. Recompile software!"; case EVENT_ERROR_OPEN_CONTACTOR: - return "Too much time spent in error state. Opening contactors, not safe to continue charging. " - "Check other error code for reason!"; + return "Too much time spent in error state. Opening contactors, not safe to continue. " + "Check other active ERROR code for reason. Reboot emulator after problem is solved!"; case EVENT_MODBUS_INVERTER_MISSING: return "Modbus inverter has not sent any data. Inspect communication wiring!"; case EVENT_NO_ENABLE_DETECTED: diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 3d4b6975..3a4dd0d9 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -1277,7 +1277,7 @@ String processor(const String& var) { content += "

Power status: " + String(get_emulator_pause_status().c_str()) + "

"; } - content += "

Battery allows contactor closing: "; + content += "

Emulator allows contactor closing: "; if (datalayer.battery.status.bms_status == FAULT) { content += ""; } else { @@ -1289,11 +1289,13 @@ String processor(const String& var) { } else { content += "

"; } - content += "

Secondary battery allowed to join "; - if (datalayer.system.status.battery2_allowed_contactor_closing == true) { - content += ""; - } else { - content += "✕ (voltage mismatch)"; + if (battery2) { + content += "

Secondary battery allowed to join "; + if (datalayer.system.status.battery2_allowed_contactor_closing == true) { + content += ""; + } else { + content += "✕ (voltage mismatch)"; + } } if (!contactor_control_enabled) { @@ -1304,13 +1306,19 @@ String processor(const String& var) { "powering the contactors. Battery-Emulator will have limited amount of control over the contactors!"; content += ""; } else { //contactor_control_enabled TRUE - content += "

Contactors controlled by emulator, state: "; - if (datalayer.system.status.contactors_engaged) { + content += "

Contactors controlled by emulator, state: "; + if (datalayer.system.status.contactors_engaged == 0) { + content += "PRECHARGE"; + } else if (datalayer.system.status.contactors_engaged == 1) { content += "ON"; - } else { + } else if (datalayer.system.status.contactors_engaged == 2) { content += "OFF"; + content += " [!]"; + content += + "Emulator spent too much time in critical FAULT event. Investigate event " + "causing this via Events page. Reboot required to resume operation!"; } - content += "

"; + content += "

"; if (contactor_control_enabled_double_battery) { if (pwm_contactor_control) { content += "

Cont. Neg.: "; From 5888457926c76287a834885582ff29f9daee7030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 17 Aug 2025 21:43:43 +0300 Subject: [PATCH 03/15] Improve secondary battery visualization --- .../comm_contactorcontrol.cpp | 2 +- Software/src/devboard/webserver/webserver.cpp | 54 ++----------------- 2 files changed, 5 insertions(+), 51 deletions(-) diff --git a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp index c63dd4ae..6ea7798a 100644 --- a/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp +++ b/Software/src/communication/contactorcontrol/comm_contactorcontrol.cpp @@ -55,7 +55,7 @@ const int OFF = 0; #define OFF 1 #endif //NC_CONTACTORS -#define MAX_ALLOWED_FAULT_TICKS 1000 +#define MAX_ALLOWED_FAULT_TICKS 1000 //1000 = 10 seconds #define NEGATIVE_CONTACTOR_TIME_MS \ 500 // Time after negative contactor is turned on, to start precharge (not actual precharge time!) #define PRECHARGE_COMPLETED_TIME_MS \ diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 3a4dd0d9..6081ac12 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -1222,47 +1222,6 @@ String processor(const String& var) { } else { // > 0 content += "

Battery charging!

"; } - - if (contactor_control_enabled) { - content += "

Contactors controlled by emulator, state: "; - if (datalayer.system.status.contactors_battery2_engaged) { - content += "ON"; - } else { - content += "OFF"; - } - content += "

"; - - if (contactor_control_enabled_double_battery) { - content += "

Cont. Neg.: "; - if (pwm_contactor_control) { - if (datalayer.system.status.contactors_battery2_engaged) { - content += "Economized"; - content += " Cont. Pos.: "; - content += "Economized"; - } else { - content += ""; - content += " Cont. Pos.: "; - content += ""; - } - } else { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded -#if defined(SECOND_POSITIVE_CONTACTOR_PIN) && defined(SECOND_NEGATIVE_CONTACTOR_PIN) - if (digitalRead(SECOND_NEGATIVE_CONTACTOR_PIN) == HIGH) { - content += ""; - } else { - content += ""; - } - - content += " Cont. Pos.: "; - if (digitalRead(SECOND_POSITIVE_CONTACTOR_PIN) == HIGH) { - content += ""; - } else { - content += ""; - } -#endif - } - content += "

"; - } - } content += ""; content += ""; } @@ -1320,25 +1279,20 @@ String processor(const String& var) { } content += "

"; if (contactor_control_enabled_double_battery) { + content += "

Secondary battery contactor, state: "; if (pwm_contactor_control) { - content += "

Cont. Neg.: "; if (datalayer.system.status.contactors_battery2_engaged) { content += "Economized"; - content += " Cont. Pos.: "; - content += "Economized"; } else { - content += ""; - content += " Cont. Pos.: "; - content += ""; + content += "OFF"; } } else if ( esp32hal->SECOND_BATTERY_CONTACTORS_PIN() != GPIO_NUM_NC) { // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded - content += "

Cont. Neg.: "; if (digitalRead(esp32hal->SECOND_BATTERY_CONTACTORS_PIN()) == HIGH) { - content += ""; + content += "ON"; } else { - content += ""; + content += "OFF"; } } //no PWM_CONTACTOR_CONTROL content += "

"; From 2f6b63afe874e3811852afa87e0886b5a018f3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 17 Aug 2025 21:53:44 +0300 Subject: [PATCH 04/15] Add check if bat2 is configured --- Software/src/devboard/webserver/webserver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 6081ac12..c1225185 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -1278,7 +1278,7 @@ String processor(const String& var) { "causing this via Events page. Reboot required to resume operation!"; } content += ""; - if (contactor_control_enabled_double_battery) { + if (contactor_control_enabled_double_battery && battery2) { content += "

Secondary battery contactor, state: "; if (pwm_contactor_control) { if (datalayer.system.status.contactors_battery2_engaged) { From 24abab77463a8305b13713ffe3f1216938b7e7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 17 Aug 2025 21:59:21 +0300 Subject: [PATCH 05/15] Fix startup voltage not triggering events --- Software/src/battery/KIA-HYUNDAI-64-BATTERY.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h index 2a986255..1e991465 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h @@ -70,7 +70,7 @@ class KiaHyundai64Battery : public CanBattery { uint16_t CellVoltMin_mV = 3700; uint16_t allowedDischargePower = 0; uint16_t allowedChargePower = 0; - uint16_t batteryVoltage = 0; + uint16_t batteryVoltage = 3700; uint16_t inverterVoltageFrameHigh = 0; uint16_t inverterVoltage = 0; uint16_t cellvoltages_mv[98]; From d86a9bd3641cae36efee7692c5816fe9bb69ad8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 20 Aug 2025 21:13:45 +0300 Subject: [PATCH 06/15] Add HVIL status to webserver --- Software/src/battery/VOLVO-SPA-BATTERY.cpp | 26 +++++++-- Software/src/battery/VOLVO-SPA-BATTERY.h | 12 +++-- Software/src/battery/VOLVO-SPA-HTML.h | 59 ++++++++++++++++++--- Software/src/datalayer/datalayer_extended.h | 2 + 4 files changed, 83 insertions(+), 16 deletions(-) diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.cpp b/Software/src/battery/VOLVO-SPA-BATTERY.cpp index dabd9f14..8b28d0df 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.cpp +++ b/Software/src/battery/VOLVO-SPA-BATTERY.cpp @@ -7,7 +7,6 @@ void VolvoSpaBattery:: update_values() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter - uint8_t cnt = 0; // Update webserver datalayer datalayer_extended.VolvoPolestar.soc_bms = SOC_BMS; @@ -88,6 +87,7 @@ void VolvoSpaBattery:: } #ifdef DEBUG_LOG + uint8_t cnt = 0; logging.print("BMS reported SOC%: "); logging.println(SOC_BMS); logging.print("Calculated SOC%: "); @@ -127,6 +127,7 @@ void VolvoSpaBattery:: logging.print(cell_voltages[cnt++]); logging.print(","); } + cnt = 0; logging.println(";"); #endif } @@ -272,18 +273,33 @@ void VolvoSpaBattery::handle_incoming_can_frame(CAN_frame rx_frame) { (rx_frame.data.u8[3] == 0x42)) // BECM module voltage supply { datalayer_extended.VolvoPolestar.BECMsupplyVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + transmit_can_frame(&VOLVO_BECM_HVIL_Status_Req); //Send HVIL status readout command + } else if ((rx_frame.data.u8[0] == 0x04) && (rx_frame.data.u8[1] == 0x62) && (rx_frame.data.u8[2] == 0x49) && + (rx_frame.data.u8[3] == 0x1A)) // BECM HVIL status + { + datalayer_extended.VolvoPolestar.HVILstatusBits = (rx_frame.data.u8[4]); + transmit_can_frame(&VOLVO_DTCreadout); //Send DTC readout command } else if ((rx_frame.data.u8[0] == 0x10) && (rx_frame.data.u8[1] == 0x0B) && (rx_frame.data.u8[2] == 0x62) && (rx_frame.data.u8[3] == 0x4B)) // First response frame of cell voltages { cell_voltages[battery_request_idx++] = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]); cell_voltages[battery_request_idx] = (rx_frame.data.u8[7] << 8); transmit_can_frame(&VOLVO_FlowControl); // Send flow control - rxConsecutiveFrames = 1; + rxConsecutiveFrames = true; } else if ((rx_frame.data.u8[0] == 0x10) && (rx_frame.data.u8[2] == 0x59) && (rx_frame.data.u8[3] == 0x03)) // First response frame for DTC with more than one code { + datalayer_extended.VolvoPolestar.DTCcount = ((rx_frame.data.u8[1] - 2) / 4); transmit_can_frame(&VOLVO_FlowControl); // Send flow control - } else if ((rx_frame.data.u8[0] == 0x21) && (rxConsecutiveFrames == 1)) { + } else if ((rx_frame.data.u8[1] == 0x59) && + (rx_frame.data.u8[2] == 0x03)) // Response frame for DTC with 0 or 1 code + { + if (rx_frame.data.u8[0] != 0x02) { + datalayer_extended.VolvoPolestar.DTCcount = 1; + } else { + datalayer_extended.VolvoPolestar.DTCcount = 0; + } + } else if ((rx_frame.data.u8[0] == 0x21) && (rxConsecutiveFrames)) { cell_voltages[battery_request_idx++] = cell_voltages[battery_request_idx] | rx_frame.data.u8[1]; cell_voltages[battery_request_idx++] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]; cell_voltages[battery_request_idx++] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; @@ -303,7 +319,7 @@ void VolvoSpaBattery::handle_incoming_can_frame(CAN_frame rx_frame) { } transmit_can_frame(&VOLVO_SOH_Req); //Send SOH read request } - rxConsecutiveFrames = 0; + rxConsecutiveFrames = false; } break; default: @@ -314,7 +330,7 @@ void VolvoSpaBattery::handle_incoming_can_frame(CAN_frame rx_frame) { void VolvoSpaBattery::readCellVoltages() { battery_request_idx = 0; batteryModuleNumber = 0x10; - rxConsecutiveFrames = 0; + rxConsecutiveFrames = false; VOLVO_CELL_U_Req.data.u8[3] = batteryModuleNumber++; transmit_can_frame(&VOLVO_CELL_U_Req); //Send cell voltage read request for first module } diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.h b/Software/src/battery/VOLVO-SPA-BATTERY.h index f9264f66..b5d4fcad 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.h +++ b/Software/src/battery/VOLVO-SPA-BATTERY.h @@ -64,7 +64,7 @@ class VolvoSpaBattery : public CanBattery { uint16_t HvBattPwrLimChrgSlowAgi = 0; //0x177 uint8_t batteryModuleNumber = 0x10; // First battery module uint8_t battery_request_idx = 0; - uint8_t rxConsecutiveFrames = 0; + bool rxConsecutiveFrames = false; uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV uint8_t cellcounter = 0; uint16_t cell_voltages[108]; //array with all the cellvoltages @@ -75,21 +75,17 @@ class VolvoSpaBattery : public CanBattery { .ext_ID = false, .DLC = 8, .ID = 0x536, - //.data = {0x00, 0x40, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame .data = {0x00, 0x40, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00}}; //Network manage frame - CAN_frame VOLVO_140_CLOSE = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x140, .data = {0x00, 0x02, 0x00, 0xB7, 0xFF, 0x03, 0xFF, 0x82}}; //Close contactors message - CAN_frame VOLVO_140_OPEN = {.FD = false, .ext_ID = false, .DLC = 8, .ID = 0x140, .data = {0x00, 0x02, 0x00, 0x9E, 0xFF, 0x03, 0xFF, 0x82}}; //Open contactor message - CAN_frame VOLVO_372 = { .FD = false, .ext_ID = false, @@ -117,6 +113,12 @@ class VolvoSpaBattery : public CanBattery { .DLC = 8, .ID = 0x735, .data = {0x03, 0x22, 0xF4, 0x42, 0x00, 0x00, 0x00, 0x00}}; //BECM supply voltage request frame + CAN_frame VOLVO_BECM_HVIL_Status_Req = { + .FD = false, + .ext_ID = false, + .DLC = 8, + .ID = 0x735, + .data = {0x03, 0x22, 0x49, 0x1A, 0x00, 0x00, 0x00, 0x00}}; //BECM HVIL status request frame CAN_frame VOLVO_DTC_Erase = {.FD = false, .ext_ID = false, .DLC = 8, diff --git a/Software/src/battery/VOLVO-SPA-HTML.h b/Software/src/battery/VOLVO-SPA-HTML.h index dd65c9a2..9d832d51 100644 --- a/Software/src/battery/VOLVO-SPA-HTML.h +++ b/Software/src/battery/VOLVO-SPA-HTML.h @@ -9,11 +9,11 @@ class VolvoSpaHtmlRenderer : public BatteryHtmlRenderer { public: String get_status_html() { String content; - - content += "

BECM reported SOC: " + String(datalayer_extended.VolvoPolestar.soc_bms) + "

"; - content += "

Calculated SOC: " + String(datalayer_extended.VolvoPolestar.soc_calc) + "

"; - content += "

Rescaled SOC: " + String(datalayer_extended.VolvoPolestar.soc_rescaled / 10) + "

"; - content += "

BECM reported SOH: " + String(datalayer_extended.VolvoPolestar.soh_bms) + "

"; + content += "

BECM reported number of DTCs: " + String(datalayer_extended.VolvoPolestar.DTCcount) + "

"; + content += "

BECM reported SOC: " + String(datalayer_extended.VolvoPolestar.soc_bms / 10.0) + " %

"; + content += "

Calculated SOC: " + String(datalayer_extended.VolvoPolestar.soc_calc / 10.0) + " %

"; + content += "

Rescaled SOC: " + String(datalayer_extended.VolvoPolestar.soc_rescaled / 10.0) + " %

"; + content += "

BECM reported SOH: " + String(datalayer_extended.VolvoPolestar.soh_bms / 100.0) + " %

"; content += "

BECM supply voltage: " + String(datalayer_extended.VolvoPolestar.BECMsupplyVoltage) + " mV

"; content += "

HV voltage: " + String(datalayer_extended.VolvoPolestar.BECMBatteryVoltage) + " V

"; @@ -31,7 +31,54 @@ class VolvoSpaHtmlRenderer : public BatteryHtmlRenderer { content += "

Charge power limit slow aging: " + String(datalayer_extended.VolvoPolestar.HvBattPwrLimChrgSlowAgi) + " kW

"; - + content += "

HVIL Circuit A status: "; + switch (datalayer_extended.VolvoPolestar.HVILstatusBits & 0x01) { + case 0x01: + content += String("Open"); + break; + default: + content += String("Not valid"); + } + content += "

HVIL Circuit B status: "; + switch (datalayer_extended.VolvoPolestar.HVILstatusBits & 0x02) { + case 0x02: + content += String("Open"); + break; + default: + content += String("Closed"); + } + content += "

HVIL Circuit C status: "; + switch (datalayer_extended.VolvoPolestar.HVILstatusBits & 0x04) { + case 0x04: + content += String("Open"); + break; + default: + content += String("Closed"); + } + content += "

Positive contactor status: "; + switch (datalayer_extended.VolvoPolestar.HVILstatusBits & 0x08) { + case 0x08: + content += String("Open"); + break; + default: + content += String("Closed"); + } + content += "

Precharge Contactor status: "; + switch (datalayer_extended.VolvoPolestar.HVILstatusBits & 0x10) { + case 0x10: + content += String("Open"); + break; + default: + content += String("Closed"); + } + content += "

Negative Contactor status: "; + switch (datalayer_extended.VolvoPolestar.HVILstatusBits & 0x20) { + case 0x20: + content += String("Open"); + break; + default: + content += String("Closed"); + } content += "

HV system relay status: "; switch (datalayer_extended.VolvoPolestar.HVSysRlySts) { case 0: diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index 165588d4..091316da 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -783,6 +783,8 @@ struct DATALAYER_INFO_VOLVO_POLESTAR { uint8_t HVSysDCRlySts1 = 0; uint8_t HVSysDCRlySts2 = 0; uint8_t HVSysIsoRMonrSts = 0; + uint8_t DTCcount = 0; + uint8_t HVILstatusBits = 0; /** User requesting DTC reset via WebUI*/ bool UserRequestDTCreset = false; /** User requesting DTC readout via WebUI*/ From 2bad7682639dcdc60f6697122fe5ce213add0994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 20 Aug 2025 21:22:58 +0300 Subject: [PATCH 07/15] Add Event for HVIL not seated --- Software/src/battery/VOLVO-SPA-BATTERY.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.cpp b/Software/src/battery/VOLVO-SPA-BATTERY.cpp index 8b28d0df..ab6a9e3d 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.cpp +++ b/Software/src/battery/VOLVO-SPA-BATTERY.cpp @@ -86,6 +86,13 @@ void VolvoSpaBattery:: } } + //Raise event if HVIL connector not seated + if ((datalayer_extended.VolvoPolestar.HVILstatusBits & 0x03) > 0) { + set_event(EVENT_HVIL_FAILURE, datalayer_extended.VolvoPolestar.HVILstatusBits); + } else { + clear_event(EVENT_HVIL_FAILURE); + } + #ifdef DEBUG_LOG uint8_t cnt = 0; logging.print("BMS reported SOC%: "); From 980d914ffdc2d2be8f6418423af0c8a17326960d Mon Sep 17 00:00:00 2001 From: Matt Holmes Date: Thu, 21 Aug 2025 08:58:55 +0100 Subject: [PATCH 08/15] Adding event_level and event_level_color to mqtt information --- Software/src/devboard/mqtt/mqtt.cpp | 30 ++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 4a814230..5e94994d 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -141,7 +141,9 @@ SensorConfig batterySensorConfigTemplate[] = { {"balancing_active_cells", "Balancing Active Cells", "", "", "", always}}; SensorConfig globalSensorConfigTemplate[] = {{"bms_status", "BMS Status", "", "", "", always}, - {"pause_status", "Pause Status", "", "", "", always}}; + {"pause_status", "Pause Status", "", "", "", always}, + {"event_level", "Event Level", "", "", "", always}, + {"event_level_color", "Event Level Color", "", "", "", always}}; static std::list sensorConfigs; @@ -311,6 +313,32 @@ static bool publish_common_info(void) { set_battery_attributes(doc, datalayer.battery2, "_2", battery2->supports_charged_energy()); } } + + EVENTS_LEVEL_TYPE event_level = get_event_level(); + doc["event_level"] = String(event_level); + + // Use Home Assistant Colors https://github.com/home-assistant/core/blob/e2fdc6a98bdd22187688e70701fc3617423a714b/homeassistant/util/color.py#L19 + String event_level_color = ""; + switch (event_level) + { + case EVENT_LEVEL_INFO: + event_level_color = "green"; + break; + case EVENT_LEVEL_WARNING: + event_level_color = "yellow"; + break; + case EVENT_LEVEL_DEBUG: + case EVENT_LEVEL_UPDATE: + event_level_color = "blue"; + break; + case EVENT_LEVEL_ERROR: + event_level_color = "red"; + default: + event_level_color = "green"; + break; + } + doc["event_level_color"] = event_level_color; + serializeJson(doc, mqtt_msg); if (mqtt_publish(state_topic.c_str(), mqtt_msg, false) == false) { #ifdef DEBUG_LOG From d4f0e188fe7d09d4970fc8ff0ffac5b98af44845 Mon Sep 17 00:00:00 2001 From: Matt Holmes Date: Thu, 21 Aug 2025 17:33:15 +0100 Subject: [PATCH 09/15] Refactoring led handler, webserver and mqtt to all use a common BE status enum rather than relying on duplicatitng logic or using led color --- Software/src/devboard/mqtt/mqtt.cpp | 28 ++----------------- Software/src/devboard/utils/events.cpp | 27 ++++++++++++++++++ Software/src/devboard/utils/events.h | 11 ++++++++ Software/src/devboard/utils/led_handler.cpp | 18 ++++++------ Software/src/devboard/webserver/webserver.cpp | 15 ++++------ 5 files changed, 55 insertions(+), 44 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 5e94994d..0f7ca0b3 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -143,7 +143,7 @@ SensorConfig batterySensorConfigTemplate[] = { SensorConfig globalSensorConfigTemplate[] = {{"bms_status", "BMS Status", "", "", "", always}, {"pause_status", "Pause Status", "", "", "", always}, {"event_level", "Event Level", "", "", "", always}, - {"event_level_color", "Event Level Color", "", "", "", always}}; + {"emulator_status", "Emulator Status", "", "", "", always}}; static std::list sensorConfigs; @@ -314,30 +314,8 @@ static bool publish_common_info(void) { } } - EVENTS_LEVEL_TYPE event_level = get_event_level(); - doc["event_level"] = String(event_level); - - // Use Home Assistant Colors https://github.com/home-assistant/core/blob/e2fdc6a98bdd22187688e70701fc3617423a714b/homeassistant/util/color.py#L19 - String event_level_color = ""; - switch (event_level) - { - case EVENT_LEVEL_INFO: - event_level_color = "green"; - break; - case EVENT_LEVEL_WARNING: - event_level_color = "yellow"; - break; - case EVENT_LEVEL_DEBUG: - case EVENT_LEVEL_UPDATE: - event_level_color = "blue"; - break; - case EVENT_LEVEL_ERROR: - event_level_color = "red"; - default: - event_level_color = "green"; - break; - } - doc["event_level_color"] = event_level_color; + doc["event_level"] = get_event_level_string(get_event_level()); + doc["emulator_status"] = get_emulator_staus_string(get_emulator_status()); serializeJson(doc, mqtt_msg); if (mqtt_publish(state_topic.c_str(), mqtt_msg, false) == false) { diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index b5ba4a8f..2838cda0 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -14,6 +14,7 @@ typedef struct { static EVENT_TYPE events; static const char* EVENTS_ENUM_TYPE_STRING[] = {EVENTS_ENUM_TYPE(GENERATE_STRING)}; static const char* EVENTS_LEVEL_TYPE_STRING[] = {EVENTS_LEVEL_TYPE(GENERATE_STRING)}; +static const char* EMULATOR_STATUS_STRING[] = {EMULATOR_STATUS(GENERATE_STRING)}; /* Local function prototypes */ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched); @@ -393,6 +394,11 @@ const char* get_event_level_string(EVENTS_ENUM_TYPE event) { return EVENTS_LEVEL_TYPE_STRING[events.entries[event].level] + 12; } +const char* get_event_level_string(EVENTS_LEVEL_TYPE event_level) { + // Return the event level but skip "EVENT_LEVEL_TYPE_" that should always be first + return EVENTS_LEVEL_TYPE_STRING[event_level] + 17; +} + const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event) { return &events.entries[event]; } @@ -401,6 +407,27 @@ EVENTS_LEVEL_TYPE get_event_level(void) { return events.level; } +EMULATOR_STATUS get_emulator_status() { + switch (events.level) { + case EVENT_LEVEL_DEBUG: + case EVENT_LEVEL_INFO: + return EMULATOR_STATUS::STATUS_OK; + case EVENT_LEVEL_WARNING: + return EMULATOR_STATUS::STATUS_WARNING; + case EVENT_LEVEL_UPDATE: + return EMULATOR_STATUS::STATUS_UPDATING; + case EVENT_LEVEL_ERROR: + return EMULATOR_STATUS::STATUS_ERROR; + default: + return EMULATOR_STATUS::STATUS_OK; + } +} + +const char* get_emulator_staus_string(EMULATOR_STATUS status) { + // Return the status string but skip "STATUS_" that should always be first + return EMULATOR_STATUS_STRING[status] + 7; +} + /* Local functions */ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) { diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index 7048ee75..0a9d41d5 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -126,6 +126,14 @@ typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE; typedef enum { EVENTS_LEVEL_TYPE(GENERATE_ENUM) } EVENTS_LEVEL_TYPE; +#define EMULATOR_STATUS(XX) \ + XX(STATUS_OK) \ + XX(STATUS_WARNING) \ + XX(STATUS_ERROR) \ + XX(STATUS_UPDATING) \ + +typedef enum { EMULATOR_STATUS(GENERATE_ENUM) } EMULATOR_STATUS; + typedef enum { EVENT_STATE_PENDING = 0, EVENT_STATE_INACTIVE, @@ -151,8 +159,11 @@ struct EventData { const char* get_event_enum_string(EVENTS_ENUM_TYPE event); String get_event_message_string(EVENTS_ENUM_TYPE event); const char* get_event_level_string(EVENTS_ENUM_TYPE event); +const char* get_event_level_string(EVENTS_LEVEL_TYPE event_level); EVENTS_LEVEL_TYPE get_event_level(void); +EMULATOR_STATUS get_emulator_status(); +const char* get_emulator_staus_string(EMULATOR_STATUS status); void init_events(void); void set_event_latched(EVENTS_ENUM_TYPE event, uint8_t data); diff --git a/Software/src/devboard/utils/led_handler.cpp b/Software/src/devboard/utils/led_handler.cpp index 73fefb4d..0440723e 100644 --- a/Software/src/devboard/utils/led_handler.cpp +++ b/Software/src/devboard/utils/led_handler.cpp @@ -53,27 +53,25 @@ void LED::exe(void) { } // Set color - switch (get_event_level()) { - case EVENT_LEVEL_INFO: + switch (get_emulator_status()) { + case EMULATOR_STATUS::STATUS_OK: color = led_color::GREEN; pixels.setPixelColor(COLOR_GREEN(brightness)); // Green pulsing LED break; - case EVENT_LEVEL_WARNING: + case EMULATOR_STATUS::STATUS_WARNING: color = led_color::YELLOW; pixels.setPixelColor(COLOR_YELLOW(brightness)); // Yellow pulsing LED break; - case EVENT_LEVEL_DEBUG: - case EVENT_LEVEL_UPDATE: - color = led_color::BLUE; - pixels.setPixelColor(COLOR_BLUE(brightness)); // Blue pulsing LED - break; - case EVENT_LEVEL_ERROR: + case EMULATOR_STATUS::STATUS_ERROR: color = led_color::RED; pixels.setPixelColor(COLOR_RED(esp32hal->LED_MAX_BRIGHTNESS())); // Red LED full brightness break; - default: + case EMULATOR_STATUS::STATUS_UPDATING: + color = led_color::BLUE; + pixels.setPixelColor(COLOR_BLUE(brightness)); // Blue pulsing LED break; } + pixels.show(); // This sends the updated pixel color to the hardware. } diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 4949bd86..95181360 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -943,21 +943,18 @@ String processor(const String& var) { content += "