From 5d0b93986cdd60f8d9137763bd72c1d260e7df1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 8 Jun 2025 20:22:50 +0300 Subject: [PATCH 01/21] Start to work on making webserver crash free --- Software/src/battery/BMW-PHEV-HTML.h | 138 +++++++++++++++++++++--- Software/src/battery/NISSAN-LEAF-HTML.h | 16 ++- 2 files changed, 138 insertions(+), 16 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-HTML.h b/Software/src/battery/BMW-PHEV-HTML.h index 812fd101..1d03ef06 100644 --- a/Software/src/battery/BMW-PHEV-HTML.h +++ b/Software/src/battery/BMW-PHEV-HTML.h @@ -15,20 +15,130 @@ class BmwPhevHtmlRenderer : public BatteryHtmlRenderer { " dV"; content += "

Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W

"; content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; - static const char* balanceText[5] = {"0 Balancing Inactive - Balancing not needed", "1 Balancing Active", - "2 Balancing Inactive - Cells not in rest break wait 10mins", - "3 Balancing Inactive", "4 Unknown"}; - content += "

Balancing: " + String((balanceText[datalayer_extended.bmwphev.balancing_status])) + "

"; - static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected", - "3 Not Activated - Pyro Intact", "4 Unknown"}; - static const char* statusText[16] = { - "Not evaluated", "OK", "Error!", "Invalid signal", "", "", "", "", "", "", "", "", "", "", "", ""}; - content += "

Interlock: " + String(statusText[datalayer_extended.bmwphev.ST_interlock]) + "

"; - content += "

Isolation external: " + String(statusText[datalayer_extended.bmwphev.ST_iso_ext]) + "

"; - content += "

Isolation internal: " + String(statusText[datalayer_extended.bmwphev.ST_iso_int]) + "

"; - content += "

Isolation: " + String(statusText[datalayer_extended.bmwphev.ST_isolation]) + "

"; - content += "

Cooling valve: " + String(statusText[datalayer_extended.bmwphev.ST_valve_cooling]) + "

"; - content += "

Emergency: " + String(statusText[datalayer_extended.bmwphev.ST_EMG]) + "

"; + content += "

Balancing: "; + switch (datalayer_extended.bmwphev.balancing_status) { + case 0: + content += String("0 Balancing Inactive - Balancing not needed

"); + break; + case 1: + content += String("1 Balancing Active"); + break; + case 2: + content += String("2 Balancing Inactive - Cells not in rest break wait 10mins"); + break; + case 3: + content += String("3 Balancing Inactive"); + break; + case 4: + content += String("4 Unknown"); + break; + default: + content += String("Unknown"); + } + content += "

Interlock: "; + switch (datalayer_extended.bmwphev.ST_interlock) { + case 0: + content += String("Not Evaluated

"); + break; + case 1: + content += String("OK"); + break; + case 2: + content += String("Error! Not seated!"); + break; + case 3: + content += String("Invalid signal"); + break; + default: + content += String("Unknown"); + } + content += "

Isolation external: "; + switch (datalayer_extended.bmwphev.ST_iso_ext) { + case 0: + content += String("Not Evaluated

"); + break; + case 1: + content += String("OK"); + break; + case 2: + content += String("Error!"); + break; + case 3: + content += String("Invalid signal"); + break; + default: + content += String("Unknown"); + } + content += "

Isolation internal: "; + switch (datalayer_extended.bmwphev.ST_iso_int) { + case 0: + content += String("Not Evaluated

"); + break; + case 1: + content += String("OK"); + break; + case 2: + content += String("Error!"); + break; + case 3: + content += String("Invalid signal"); + break; + default: + content += String("Unknown"); + } + content += "

Isolation: "; + switch (datalayer_extended.bmwphev.ST_isolation) { + case 0: + content += String("Not Evaluated

"); + break; + case 1: + content += String("OK"); + break; + case 2: + content += String("Error!"); + break; + case 3: + content += String("Invalid signal"); + break; + default: + content += String("Unknown"); + } + content += "

Cooling valve: "; + switch (datalayer_extended.bmwphev.ST_valve_cooling) { + case 0: + content += String("Not Evaluated

"); + break; + case 1: + content += String("OK"); + break; + case 2: + content += String("Error!"); + break; + case 3: + content += String("Invalid signal"); + break; + default: + content += String("Unknown"); + } + content += "

Emergency: "; + switch (datalayer_extended.bmwphev.ST_EMG) { + case 0: + content += String("Not Evaluated

"); + break; + case 1: + content += String("OK"); + break; + case 2: + content += String("Error!"); + break; + case 3: + content += String("Invalid signal"); + break; + default: + content += String("Unknown"); + } + + static const char* prechargeText[16] = {"Not evaluated", "Not active, closing not blocked", "Error precharge blocked", diff --git a/Software/src/battery/NISSAN-LEAF-HTML.h b/Software/src/battery/NISSAN-LEAF-HTML.h index 18aac585..275bef7b 100644 --- a/Software/src/battery/NISSAN-LEAF-HTML.h +++ b/Software/src/battery/NISSAN-LEAF-HTML.h @@ -10,8 +10,20 @@ class NissanLeafHtmlRenderer : public BatteryHtmlRenderer { String get_status_html() { String content; - static const char* LEAFgen[] = {"ZE0", "AZE0", "ZE1"}; - content += "

LEAF generation: " + String(LEAFgen[datalayer_extended.nissanleaf.LEAF_gen]) + "

"; + content += "

LEAF generation: "; + switch (datalayer_extended.nissanleaf.LEAF_gen) { + case 0: + content += String("ZE0

"); + break; + case 1: + content += String("AZE0"); + break; + case 2: + content += String("ZE1"); + break; + default: + content += String("Unknown"); + } char readableSerialNumber[16]; // One extra space for null terminator memcpy(readableSerialNumber, datalayer_extended.nissanleaf.BatterySerialNumber, sizeof(datalayer_extended.nissanleaf.BatterySerialNumber)); From 289fa0d16ef80d92979cf000895c6fd1d382efd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 10 Jun 2025 13:07:22 +0300 Subject: [PATCH 02/21] Make PHEV HTML array crash proof --- Software/src/battery/BMW-PHEV-HTML.h | 223 ++++++++++++++------------- 1 file changed, 113 insertions(+), 110 deletions(-) diff --git a/Software/src/battery/BMW-PHEV-HTML.h b/Software/src/battery/BMW-PHEV-HTML.h index 1d03ef06..3b171d41 100644 --- a/Software/src/battery/BMW-PHEV-HTML.h +++ b/Software/src/battery/BMW-PHEV-HTML.h @@ -17,198 +17,201 @@ class BmwPhevHtmlRenderer : public BatteryHtmlRenderer { content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; content += "

Balancing: "; switch (datalayer_extended.bmwphev.balancing_status) { - case 0: + case 0: content += String("0 Balancing Inactive - Balancing not needed

"); break; - case 1: + case 1: content += String("1 Balancing Active"); break; - case 2: + case 2: content += String("2 Balancing Inactive - Cells not in rest break wait 10mins"); break; - case 3: + case 3: content += String("3 Balancing Inactive"); break; - case 4: + case 4: content += String("4 Unknown"); break; - default: + default: content += String("Unknown"); } - content += "

Interlock: "; + content += "

Interlock: "; switch (datalayer_extended.bmwphev.ST_interlock) { - case 0: + case 0: content += String("Not Evaluated

"); break; - case 1: + case 1: content += String("OK"); break; - case 2: + case 2: content += String("Error! Not seated!"); break; - case 3: + case 3: content += String("Invalid signal"); break; - default: + default: content += String("Unknown"); } - content += "

Isolation external: "; + content += "

Isolation external: "; switch (datalayer_extended.bmwphev.ST_iso_ext) { - case 0: + case 0: content += String("Not Evaluated

"); break; - case 1: + case 1: content += String("OK"); break; - case 2: + case 2: content += String("Error!"); break; - case 3: + case 3: content += String("Invalid signal"); break; - default: + default: content += String("Unknown"); } content += "

Isolation internal: "; switch (datalayer_extended.bmwphev.ST_iso_int) { - case 0: + case 0: content += String("Not Evaluated

"); break; - case 1: + case 1: content += String("OK"); break; - case 2: + case 2: content += String("Error!"); break; - case 3: + case 3: content += String("Invalid signal"); break; - default: + default: content += String("Unknown"); } content += "

Isolation: "; switch (datalayer_extended.bmwphev.ST_isolation) { - case 0: + case 0: content += String("Not Evaluated

"); break; - case 1: + case 1: content += String("OK"); break; - case 2: + case 2: content += String("Error!"); break; - case 3: + case 3: content += String("Invalid signal"); break; - default: + default: content += String("Unknown"); } content += "

Cooling valve: "; switch (datalayer_extended.bmwphev.ST_valve_cooling) { - case 0: + case 0: content += String("Not Evaluated

"); break; - case 1: + case 1: content += String("OK"); break; - case 2: + case 2: content += String("Error!"); break; - case 3: + case 3: content += String("Invalid signal"); break; - default: + default: content += String("Unknown"); } content += "

Emergency: "; switch (datalayer_extended.bmwphev.ST_EMG) { - case 0: + case 0: content += String("Not Evaluated

"); break; - case 1: + case 1: content += String("OK"); break; - case 2: + case 2: content += String("Error!"); break; - case 3: + case 3: content += String("Invalid signal"); break; - default: + default: content += String("Unknown"); } - - - static const char* prechargeText[16] = {"Not evaluated", - "Not active, closing not blocked", - "Error precharge blocked", - "Invalid signal", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - ""}; - content += "

Precharge: " + String(prechargeText[datalayer_extended.bmwphev.ST_precharge]) + - "

"; //Still unclear of enum - static const char* DCSWText[16] = {"Contactors open", - "Precharge ongoing", - "Contactors engaged", - "Invalid signal", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - ""}; - content += "

Contactor status: " + String(DCSWText[datalayer_extended.bmwphev.ST_DCSW]) + "

"; - static const char* contText[16] = {"Contactors OK", - "One contactor welded!", - "Two contactors welded!", - "Invalid signal", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - ""}; - content += "

Contactor weld: " + String(contText[datalayer_extended.bmwphev.ST_WELD]) + "

"; - static const char* valveText[16] = {"OK", - "Short circuit to GND", - "Short circuit to 12V", - "Line break", - "", - "", - "Driver error", - "", - "", - "", - "", - "", - "Stuck", - "Stuck", - "", - "Invalid Signal"}; - content += - "

Cold shutoff valve: " + String(valveText[datalayer_extended.bmwphev.ST_cold_shutoff_valve]) + "

"; + content += "

Precharge: "; + switch (datalayer_extended.bmwphev.ST_precharge) { + case 0: + content += String("Not Evaluated

"); + break; + case 1: + content += String("Not active, closing not blocked"); + break; + case 2: + content += String("Error precharge blocked"); + break; + case 3: + content += String("Invalid signal"); + break; + default: + content += String("Unknown"); //Still unclear of enum + } + content += "

Contactor status: "; + switch (datalayer_extended.bmwphev.ST_DCSW) { + case 0: + content += String("Contactors open

"); + break; + case 1: + content += String("Precharge ongoing"); + break; + case 2: + content += String("Contactors engaged"); + break; + case 3: + content += String("Invalid signal"); + break; + default: + content += String("Unknown"); + } + content += "

Contactor weld: "; + switch (datalayer_extended.bmwphev.ST_WELD) { + case 0: + content += String("Contactors OK

"); + break; + case 1: + content += String("One contactor welded!"); + break; + case 2: + content += String("Two contactors welded!"); + break; + case 3: + content += String("Invalid signal"); + break; + default: + content += String("Unknown"); + } + content += "

Cold shutoff valve: "; + switch (datalayer_extended.bmwphev.ST_cold_shutoff_valve) { + case 0: + content += String("OK

"); + break; + case 1: + content += String("Short circuit to GND"); + break; + case 2: + content += String("Short circuit to 12V"); + break; + case 3: + content += String("Line break"); + break; + case 6: + content += String("Driver error"); + break; + case 12: + case 13: + content += String("Stuck"); + break; + default: + content += String("Invalid Signal"); + } content += "

Min Cell Voltage Data Age: " + String(datalayer_extended.bmwphev.min_cell_voltage_data_age) + " ms

"; content += From 6dedcf0a405447afa6a9e34226adc3032202a4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 10 Jun 2025 13:16:42 +0300 Subject: [PATCH 03/21] Make iX HTML arrays crash proof --- Software/src/battery/BMW-IX-HTML.h | 103 +++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 12 deletions(-) diff --git a/Software/src/battery/BMW-IX-HTML.h b/Software/src/battery/BMW-IX-HTML.h index 64b88fb9..02be12eb 100644 --- a/Software/src/battery/BMW-IX-HTML.h +++ b/Software/src/battery/BMW-IX-HTML.h @@ -25,13 +25,37 @@ class BmwIXHtmlRenderer : public BatteryHtmlRenderer { content += "

Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W

"; content += "

T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV

"; content += "

Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "

"; - static const char* balanceText[5] = {"0 No balancing mode active", "1 Voltage-Controlled Balancing Mode", - "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging", - "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage", - "4 No balancing mode active, qualifier invalid"}; - content += "

Balancing: " + String((balanceText[datalayer_extended.bmwix.balancing_status])) + "

"; - static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"}; - content += "

HVIL Status: " + String(hvilText[datalayer_extended.bmwix.hvil_status]) + "

"; + content += "

Balancing: "; + switch (datalayer_extended.bmwix.balancing_status) { + case 0: + content += String("0 No balancing mode active

"); + break; + case 1: + content += String("1 Voltage-Controlled Balancing Mode"); + break; + case 2: + content += String("2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging"); + break; + case 3: + content += String("3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage"); + break; + case 4: + content += String("4 No balancing mode active, qualifier invalid"); + break; + default: + content += String("Unknown"); + } + content += "

HVIL Status: "; + switch (datalayer_extended.bmwix.hvil_status) { + case 0: + content += String("Error (Loop Open)

"); + break; + case 1: + content += String("OK (Loop Closed)"); + break; + default: + content += String("Unknown"); + } content += "

BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds

"; content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwix.allowable_charge_amps) + " A

"; content += @@ -41,11 +65,66 @@ class BmwIXHtmlRenderer : public BatteryHtmlRenderer { content += "

Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm

"; content += "

Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm

"; content += "

Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm

"; - static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected", - "3 Not Activated - Pyro Intact", "4 Unknown"}; - content += "

Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss1])) + "

"; - content += "

Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss4])) + "

"; - content += "

Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss6])) + "

"; + content += "

Pyro Status PSS1: "; + switch (datalayer_extended.bmwix.pyro_status_pss1) { + case 0: + content += String("0 Value Invalid

"); + break; + case 1: + content += String("1 Successfully Blown"); + break; + case 2: + content += String("2 Disconnected"); + break; + case 3: + content += String("3 Not Activated - Pyro Intact"); + break; + case 4: + content += String("4 Unknown"); + break; + default: + content += String("Unknown"); + } + content += "

Pyro Status PSS4: "; + switch (datalayer_extended.bmwix.pyro_status_pss4) { + case 0: + content += String("0 Value Invalid

"); + break; + case 1: + content += String("1 Successfully Blown"); + break; + case 2: + content += String("2 Disconnected"); + break; + case 3: + content += String("3 Not Activated - Pyro Intact"); + break; + case 4: + content += String("4 Unknown"); + break; + default: + content += String("Unknown"); + } + content += "

Pyro Status PSS6: "; + switch (datalayer_extended.bmwix.pyro_status_pss6) { + case 0: + content += String("0 Value Invalid

"); + break; + case 1: + content += String("1 Successfully Blown"); + break; + case 2: + content += String("2 Disconnected"); + break; + case 3: + content += String("3 Not Activated - Pyro Intact"); + break; + case 4: + content += String("4 Unknown"); + break; + default: + content += String("Unknown"); + } return content; } From ae460f98262775bb264b6c76f7174b09f5d64da6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:20:06 +0000 Subject: [PATCH 04/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Software/src/battery/NISSAN-LEAF-HTML.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/NISSAN-LEAF-HTML.h b/Software/src/battery/NISSAN-LEAF-HTML.h index 275bef7b..4d21d4dd 100644 --- a/Software/src/battery/NISSAN-LEAF-HTML.h +++ b/Software/src/battery/NISSAN-LEAF-HTML.h @@ -10,7 +10,7 @@ class NissanLeafHtmlRenderer : public BatteryHtmlRenderer { String get_status_html() { String content; - content += "

LEAF generation: "; + content += "

LEAF generation: "; switch (datalayer_extended.nissanleaf.LEAF_gen) { case 0: content += String("ZE0

"); From c4503551030a3d97d599e27c2e0bf980dea18803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 12 Jun 2025 23:49:25 +0300 Subject: [PATCH 05/21] Add support for Double CMFA-EV batteries --- Software/src/battery/CMFA-EV-BATTERY.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Software/src/battery/CMFA-EV-BATTERY.h b/Software/src/battery/CMFA-EV-BATTERY.h index 0455838e..c5aff585 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.h +++ b/Software/src/battery/CMFA-EV-BATTERY.h @@ -10,6 +10,23 @@ class CmfaEvBattery : public CanBattery { public: + // Use this constructor for the second battery. + CmfaEvBattery(DATALAYER_BATTERY_TYPE* datalayer_ptr, DATALAYER_INFO_CMFAEV* extended, CAN_Interface targetCan) + : CanBattery(targetCan) { + datalayer_battery = datalayer_ptr; + allows_contactor_closing = nullptr; + datalayer_cmfa = extended; + + average_voltage_of_cells = 0; + } + + // Use the default constructor to create the first or single battery. + CmfaEvBattery() { + datalayer_battery = &datalayer.battery; + allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing; + datalayer_cmfa = &datalayer_extended.CMFAEV; + } + virtual void setup(void); virtual void handle_incoming_can_frame(CAN_frame rx_frame); virtual void update_values(); @@ -20,6 +37,12 @@ class CmfaEvBattery : public CanBattery { private: CmfaEvHtmlRenderer renderer; + DATALAYER_BATTERY_TYPE* datalayer_battery; + DATALAYER_INFO_CMFAEV* datalayer_cmfa; + + // If not null, this battery decides when the contactor can be closed and writes the value here. + bool* allows_contactor_closing; + uint16_t rescale_raw_SOC(uint32_t raw_SOC); static const int MAX_PACK_VOLTAGE_DV = 3040; // 5000 = 500.0V From 8b767a10c51bd62d242d54f7596912c283dc6462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 13 Jun 2025 13:59:44 +0300 Subject: [PATCH 06/21] Update datalayer writing --- Software/src/battery/CMFA-EV-BATTERY.cpp | 94 ++++++++++++------------ 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/Software/src/battery/CMFA-EV-BATTERY.cpp b/Software/src/battery/CMFA-EV-BATTERY.cpp index e9eddf64..9e2e940c 100644 --- a/Software/src/battery/CMFA-EV-BATTERY.cpp +++ b/Software/src/battery/CMFA-EV-BATTERY.cpp @@ -28,68 +28,68 @@ uint16_t CmfaEvBattery::rescale_raw_SOC(uint32_t raw_SOC) { void CmfaEvBattery:: update_values() { //This function maps all the values fetched via CAN to the correct parameters used for modbus - datalayer.battery.status.soh_pptt = (SOH * 100); + datalayer_battery->status.soh_pptt = (SOH * 100); - datalayer.battery.status.real_soc = rescale_raw_SOC(SOC_raw); + datalayer_battery->status.real_soc = rescale_raw_SOC(SOC_raw); - datalayer.battery.status.current_dA = current * 10; + datalayer_battery->status.current_dA = current * 10; - datalayer.battery.status.voltage_dV = average_voltage_of_cells / 100; + datalayer_battery->status.voltage_dV = average_voltage_of_cells / 100; - datalayer.battery.info.total_capacity_Wh = 27000; + datalayer_battery->info.total_capacity_Wh = 27000; //Calculate the remaining Wh amount from SOC% and max Wh value. - datalayer.battery.status.remaining_capacity_Wh = static_cast( - (static_cast(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh); + datalayer_battery->status.remaining_capacity_Wh = static_cast( + (static_cast(datalayer_battery->status.real_soc) / 10000) * datalayer_battery->info.total_capacity_Wh); - datalayer.battery.status.max_discharge_power_W = discharge_power_w; + datalayer_battery->status.max_discharge_power_W = discharge_power_w; - datalayer.battery.status.max_charge_power_W = charge_power_w; + datalayer_battery->status.max_charge_power_W = charge_power_w; - datalayer.battery.status.temperature_min_dC = (lowest_cell_temperature * 10); + datalayer_battery->status.temperature_min_dC = (lowest_cell_temperature * 10); - datalayer.battery.status.temperature_max_dC = (highest_cell_temperature * 10); + datalayer_battery->status.temperature_max_dC = (highest_cell_temperature * 10); - datalayer.battery.status.cell_min_voltage_mV = lowest_cell_voltage_mv; + datalayer_battery->status.cell_min_voltage_mV = lowest_cell_voltage_mv; - datalayer.battery.status.cell_max_voltage_mV = highest_cell_voltage_mv; + datalayer_battery->status.cell_max_voltage_mV = highest_cell_voltage_mv; //Map all cell voltages to the global array - memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mv, 72 * sizeof(uint16_t)); + memcpy(datalayer_battery->status.cell_voltages_mV, cellvoltages_mv, 72 * sizeof(uint16_t)); if (lead_acid_voltage < 11000) { //11.000V set_event(EVENT_12V_LOW, lead_acid_voltage); } // Update webserver datalayer - datalayer_extended.CMFAEV.soc_u = soc_u; - datalayer_extended.CMFAEV.soc_z = soc_z; - datalayer_extended.CMFAEV.lead_acid_voltage = lead_acid_voltage; - datalayer_extended.CMFAEV.highest_cell_voltage_number = highest_cell_voltage_number; - datalayer_extended.CMFAEV.lowest_cell_voltage_number = lowest_cell_voltage_number; - datalayer_extended.CMFAEV.max_regen_power = max_regen_power; - datalayer_extended.CMFAEV.max_discharge_power = max_discharge_power; - datalayer_extended.CMFAEV.average_temperature = average_temperature; - datalayer_extended.CMFAEV.minimum_temperature = minimum_temperature; - datalayer_extended.CMFAEV.maximum_temperature = maximum_temperature; - datalayer_extended.CMFAEV.maximum_charge_power = maximum_charge_power; - datalayer_extended.CMFAEV.SOH_available_power = SOH_available_power; - datalayer_extended.CMFAEV.SOH_generated_power = SOH_generated_power; - datalayer_extended.CMFAEV.cumulative_energy_when_discharging = cumulative_energy_when_discharging; - datalayer_extended.CMFAEV.cumulative_energy_when_charging = cumulative_energy_when_charging; - datalayer_extended.CMFAEV.cumulative_energy_in_regen = cumulative_energy_in_regen; - datalayer_extended.CMFAEV.soh_average = soh_average; + datalayer_cmfa->soc_u = soc_u; + datalayer_cmfa->soc_z = soc_z; + datalayer_cmfa->lead_acid_voltage = lead_acid_voltage; + datalayer_cmfa->highest_cell_voltage_number = highest_cell_voltage_number; + datalayer_cmfa->lowest_cell_voltage_number = lowest_cell_voltage_number; + datalayer_cmfa->max_regen_power = max_regen_power; + datalayer_cmfa->max_discharge_power = max_discharge_power; + datalayer_cmfa->average_temperature = average_temperature; + datalayer_cmfa->minimum_temperature = minimum_temperature; + datalayer_cmfa->maximum_temperature = maximum_temperature; + datalayer_cmfa->maximum_charge_power = maximum_charge_power; + datalayer_cmfa->SOH_available_power = SOH_available_power; + datalayer_cmfa->SOH_generated_power = SOH_generated_power; + datalayer_cmfa->cumulative_energy_when_discharging = cumulative_energy_when_discharging; + datalayer_cmfa->cumulative_energy_when_charging = cumulative_energy_when_charging; + datalayer_cmfa->cumulative_energy_in_regen = cumulative_energy_in_regen; + datalayer_cmfa->soh_average = soh_average; } void CmfaEvBattery::handle_incoming_can_frame(CAN_frame rx_frame) { switch (rx_frame.ID) { //These frames are transmitted by the battery case 0x127: //10ms , Same structure as old Zoe 0x155 message! - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; current = (((((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 0.25) - 500); SOC_raw = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); break; case 0x3D6: //100ms, Same structure as old Zoe 0x424 message! - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; charge_power_w = rx_frame.data.u8[2] * 500; discharge_power_w = rx_frame.data.u8[3] * 500; lowest_cell_temperature = (rx_frame.data.u8[4] - 40); @@ -98,34 +98,34 @@ void CmfaEvBattery::handle_incoming_can_frame(CAN_frame rx_frame) { highest_cell_temperature = (rx_frame.data.u8[7] - 40); break; case 0x3D7: //100ms - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; pack_voltage = ((rx_frame.data.u8[6] << 4 | (rx_frame.data.u8[5] & 0x0F))); break; case 0x3D8: //100ms - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; //counter_3D8 = rx_frame.data.u8[3]; //? //CRC_3D8 = rx_frame.data.u8[4]; //? break; case 0x43C: //100ms - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; heartbeat2 = rx_frame.data.u8[2]; //Alternates between 0x55 and 0xAA every 5th frame break; case 0x431: //100ms - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; //byte0 9C always //byte1 40 always break; case 0x5A9: - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; case 0x5AB: - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; case 0x5C8: - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; case 0x5E1: - datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + datalayer_battery->status.CAN_battery_still_alive = CAN_STILL_ALIVE; break; case 0x7BB: // Reply from battery if (rx_frame.data.u8[0] == 0x10) { //PID header @@ -944,12 +944,12 @@ void CmfaEvBattery::setup(void) { // Performs one time setup at startup strncpy(datalayer.system.info.battery_protocol, "CMFA platform, 27 kWh battery", 63); datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.status.battery_allows_contactor_closing = true; - datalayer.battery.info.number_of_cells = 72; - datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; - datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; - datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; - datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; - datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; + datalayer_battery->info.number_of_cells = 72; + datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; + datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; + datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; + datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV; + datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; } #endif //CMFA_EV_BATTERY From 95957a503c8c16bd2c031634d7ba53b3502d980f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 15 Jun 2025 12:04:13 +0300 Subject: [PATCH 07/21] Remove unecessary string calls --- Software/src/battery/BMW-IX-HTML.h | 54 +++++++------- Software/src/battery/MEB-HTML.h | 116 ++++++++++++++--------------- 2 files changed, 85 insertions(+), 85 deletions(-) diff --git a/Software/src/battery/BMW-IX-HTML.h b/Software/src/battery/BMW-IX-HTML.h index 02be12eb..27bd3d56 100644 --- a/Software/src/battery/BMW-IX-HTML.h +++ b/Software/src/battery/BMW-IX-HTML.h @@ -28,33 +28,33 @@ class BmwIXHtmlRenderer : public BatteryHtmlRenderer { content += "

Balancing: "; switch (datalayer_extended.bmwix.balancing_status) { case 0: - content += String("0 No balancing mode active

"); + content += "0 No balancing mode active"; break; case 1: - content += String("1 Voltage-Controlled Balancing Mode"); + content += "1 Voltage-Controlled Balancing Mode"; break; case 2: - content += String("2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging"); + content += "2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging"; break; case 3: - content += String("3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage"); + content += "3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage"; break; case 4: - content += String("4 No balancing mode active, qualifier invalid"); + content += "4 No balancing mode active, qualifier invalid"; break; default: - content += String("Unknown"); + content += "Unknown"; } content += "

HVIL Status: "; switch (datalayer_extended.bmwix.hvil_status) { case 0: - content += String("Error (Loop Open)

"); + content += "Error (Loop Open)"; break; case 1: - content += String("OK (Loop Closed)"); + content += "OK (Loop Closed)"; break; default: - content += String("Unknown"); + content += "Unknown"; } content += "

BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds

"; content += "

BMS Allowed Charge Amps: " + String(datalayer_extended.bmwix.allowable_charge_amps) + " A

"; @@ -68,62 +68,62 @@ class BmwIXHtmlRenderer : public BatteryHtmlRenderer { content += "

Pyro Status PSS1: "; switch (datalayer_extended.bmwix.pyro_status_pss1) { case 0: - content += String("0 Value Invalid

"); + content += "0 Value Invalid"; break; case 1: - content += String("1 Successfully Blown"); + content += "1 Successfully Blown"; break; case 2: - content += String("2 Disconnected"); + content += "2 Disconnected"; break; case 3: - content += String("3 Not Activated - Pyro Intact"); + content += "3 Not Activated - Pyro Intact"; break; case 4: - content += String("4 Unknown"); + content += "4 Unknown"; break; default: - content += String("Unknown"); + content += "Unknown"; } content += "

Pyro Status PSS4: "; switch (datalayer_extended.bmwix.pyro_status_pss4) { case 0: - content += String("0 Value Invalid

"); + content += "0 Value Invalid"; break; case 1: - content += String("1 Successfully Blown"); + content += "1 Successfully Blown"; break; case 2: - content += String("2 Disconnected"); + content += "2 Disconnected"; break; case 3: - content += String("3 Not Activated - Pyro Intact"); + content += "3 Not Activated - Pyro Intact"; break; case 4: - content += String("4 Unknown"); + content += "4 Unknown"; break; default: - content += String("Unknown"); + content += "Unknown"; } content += "

Pyro Status PSS6: "; switch (datalayer_extended.bmwix.pyro_status_pss6) { case 0: - content += String("0 Value Invalid

"); + content += "0 Value Invalid"; break; case 1: - content += String("1 Successfully Blown"); + content += "1 Successfully Blown"; break; case 2: - content += String("2 Disconnected"); + content += "2 Disconnected"; break; case 3: - content += String("3 Not Activated - Pyro Intact"); + content += "3 Not Activated - Pyro Intact"; break; case 4: - content += String("4 Unknown"); + content += "4 Unknown"; break; default: - content += String("Unknown"); + content += "Unknown"; } return content; diff --git a/Software/src/battery/MEB-HTML.h b/Software/src/battery/MEB-HTML.h index 401236fd..20363276 100644 --- a/Software/src/battery/MEB-HTML.h +++ b/Software/src/battery/MEB-HTML.h @@ -20,119 +20,119 @@ class MebHtmlRenderer : public BatteryHtmlRenderer { content += "

HVIL status: "; switch (datalayer_extended.meb.HVIL) { case 0: - content += String("Init"); + content += "Init"; break; case 1: - content += String("Closed"); + content += "Closed"; break; case 2: - content += String("Open!"); + content += "Open!"; break; case 3: - content += String("Fault"); + content += "Fault"; break; default: - content += String("?"); + content += "?"; } content += "

KL30C status: "; switch (datalayer_extended.meb.BMS_Kl30c_Status) { case 0: - content += String("Init"); + content += "Init"; break; case 1: - content += String("Closed"); + content += "Closed"; break; case 2: - content += String("Open!"); + content += "Open!"; break; case 3: - content += String("Fault"); + content += "Fault"; break; default: - content += String("?"); + content += "?"; } content += "

BMS mode: "; switch (datalayer_extended.meb.BMS_mode) { case 0: - content += String("HV inactive"); + content += "HV inactive"; break; case 1: - content += String("HV active"); + content += "HV active"; break; case 2: - content += String("Balancing"); + content += "Balancing"; break; case 3: - content += String("Extern charging"); + content += "Extern charging"; break; case 4: - content += String("AC charging"); + content += "AC charging"; break; case 5: - content += String("Battery error"); + content += "Battery error"; break; case 6: - content += String("DC charging"); + content += "DC charging"; break; case 7: - content += String("Init"); + content += "Init"; break; default: - content += String("?"); + content += "?"; } content += String("

Charging: ") + (datalayer_extended.meb.charging_active ? "active" : "not active"); content += String("

Balancing: "); switch (datalayer_extended.meb.balancing_active) { case 0: - content += String("init"); + content += "init"; break; case 1: - content += String("active"); + content += "active"; break; case 2: - content += String("inactive"); + content += "inactive"; break; default: - content += String("?"); + content += "?"; } content += String("

Slow charging: ") + (datalayer_extended.meb.balancing_request ? "requested" : "not requested"); content += "

Diagnostic: "; switch (datalayer_extended.meb.battery_diagnostic) { case 0: - content += String("Init"); + content += "Init"; break; case 1: - content += String("Battery display"); + content += "Battery display"; break; case 4: - content += String("Battery display OK"); + content += "Battery display OK"; break; case 6: - content += String("Battery display check"); + content += "Battery display check"; break; case 7: - content += String("Fault"); + content += "Fault"; break; default: - content += String("?"); + content += "?"; } content += "

HV line status: "; switch (datalayer_extended.meb.status_HV_line) { case 0: - content += String("Init"); + content += "Init"; break; case 1: - content += String("No open HV line detected"); + content += "No open HV line detected"; break; case 2: - content += String("Open HV line"); + content += "Open HV line"; break; case 3: - content += String("Fault"); + content += "Fault"; break; default: - content += String("? ") + String(datalayer_extended.meb.status_HV_line); + content += "? " + String(datalayer_extended.meb.status_HV_line); } content += "

"; content += datalayer_extended.meb.BMS_fault_performance ? "

BMS fault performance: Active!

" @@ -147,83 +147,83 @@ class MebHtmlRenderer : public BatteryHtmlRenderer { content += "

Welded contactors: "; switch (datalayer_extended.meb.BMS_welded_contactors_status) { case 0: - content += String("Init"); + content += "Init"; break; case 1: - content += String("No contactor welded"); + content += "No contactor welded"; break; case 2: - content += String("At least 1 contactor welded"); + content += "At least 1 contactor welded"; break; case 3: - content += String("Protection status detection error"); + content += "Protection status detection error"; break; default: - content += String("?"); + content += "?"; } content += "

Warning support: "; switch (datalayer_extended.meb.warning_support) { case 0: - content += String("OK"); + content += "OK"; break; case 1: - content += String("Not OK"); + content += "Not OK"; break; case 6: - content += String("Init"); + content += "Init"; break; case 7: - content += String("Fault"); + content += "Fault"; break; default: - content += String("?"); + content += "?"; } content += "

Interm. Voltage (" + String(datalayer_extended.meb.BMS_voltage_intermediate_dV / 10.0, 1) + "V) status: "; switch (datalayer_extended.meb.BMS_status_voltage_free) { case 0: - content += String("Init"); + content += "Init"; break; case 1: - content += String("BMS interm circuit voltage free (U<20V)"); + content += "BMS interm circuit voltage free (U<20V)"; break; case 2: - content += String("BMS interm circuit not voltage free (U >= 25V)"); + content += "BMS interm circuit not voltage free (U >= 25V)"; break; case 3: - content += String("Error"); + content += "Error"; break; default: - content += String("?"); + content += "?"; } content += "

BMS error status: "; switch (datalayer_extended.meb.BMS_error_status) { case 0: - content += String("Component IO"); + content += "Component IO"; break; case 1: - content += String("Iso Error 1"); + content += "Iso Error 1"; break; case 2: - content += String("Iso Error 2"); + content += "Iso Error 2"; break; case 3: - content += String("Interlock"); + content += "Interlock"; break; case 4: - content += String("SD"); + content += "SD"; break; case 5: - content += String("Performance red"); + content += "Performance red"; break; case 6: - content += String("No component function"); + content += "No component function"; break; case 7: - content += String("Init"); + content += "Init"; break; default: - content += String("?"); + content += "?"; } content += "

BMS voltage: " + String(datalayer_extended.meb.BMS_voltage_dV / 10.0, 1) + "

"; content += datalayer_extended.meb.BMS_OBD_MIL ? "

OBD MIL: ON!

" : "

OBD MIL: Off

"; From 3368bea47153b82d9051ddda45308f29970f59db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 15 Jun 2025 18:07:24 +0300 Subject: [PATCH 08/21] Invert sign on current reading --- Software/src/battery/ECMP-BATTERY.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Software/src/battery/ECMP-BATTERY.cpp b/Software/src/battery/ECMP-BATTERY.cpp index dbab7c0f..adeb6814 100644 --- a/Software/src/battery/ECMP-BATTERY.cpp +++ b/Software/src/battery/ECMP-BATTERY.cpp @@ -8,8 +8,7 @@ /* TODO: This integration is still ongoing. Here is what still needs to be done in order to use this battery type -- Disable the isolation resistance requirement that opens contactors after 30s -- Battery says it might need 37E and 485, but no logs of this? +- Disable the isolation resistance requirement that opens contactors after 30s under load. Factory mode? */ /* Do not change code below unless you are sure what you are doing */ @@ -21,7 +20,7 @@ void EcmpBattery::update_values() { datalayer.battery.status.voltage_dV = battery_voltage * 10; - datalayer.battery.status.current_dA = battery_current * 10; + datalayer.battery.status.current_dA = -(battery_current * 10); datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt ((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100); From 1e18c119d9faa72e5244d07b7f7429a2875380a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 15 Jun 2025 18:21:25 +0300 Subject: [PATCH 09/21] Remove lots of unnecessary CAN sending --- Software/src/battery/ECMP-BATTERY.cpp | 52 +++++++++++++++------------ Software/src/battery/ECMP-BATTERY.h | 6 ++-- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/Software/src/battery/ECMP-BATTERY.cpp b/Software/src/battery/ECMP-BATTERY.cpp index adeb6814..74207f88 100644 --- a/Software/src/battery/ECMP-BATTERY.cpp +++ b/Software/src/battery/ECMP-BATTERY.cpp @@ -1321,9 +1321,11 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) { transmit_can_frame(&ECMP_0C5, can_config.battery); //DC2_0C5 transmit_can_frame(&ECMP_17B, can_config.battery); //VCU_PCANInfo_17B transmit_can_frame(&ECMP_0F2, can_config.battery); //CtrlMCU1_0F2 - transmit_can_frame(&ECMP_111, can_config.battery); - transmit_can_frame(&ECMP_110, can_config.battery); - transmit_can_frame(&ECMP_114, can_config.battery); + if (simulateEntireCar) { + transmit_can_frame(&ECMP_111, can_config.battery); + transmit_can_frame(&ECMP_110, can_config.battery); + transmit_can_frame(&ECMP_114, can_config.battery); + } } // Send 20ms periodic CAN Message simulating the car still being attached @@ -1355,7 +1357,7 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) { ECMP_27A.data = {0x4F, 0x58, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00}; } transmit_can_frame(&ECMP_230, can_config.battery); //OBC3_230 - transmit_can_frame(&ECMP_27A, can_config.battery); //PSA specific VCU message (VCU_BSI_Wakeup_27A) + transmit_can_frame(&ECMP_27A, can_config.battery); //VCU_BSI_Wakeup_27A } // Send 100ms periodic CAN Message simulating the car still being attached if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { @@ -1463,20 +1465,23 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) { transmit_can_frame(&ECMP_345, can_config.battery); //DC1_345 transmit_can_frame(&ECMP_3A2, can_config.battery); //OBC2_3A2 transmit_can_frame(&ECMP_3A3, can_config.battery); //OBC1_3A3 - transmit_can_frame(&ECMP_31E, can_config.battery); - transmit_can_frame(&ECMP_383, can_config.battery); - transmit_can_frame(&ECMP_010, can_config.battery); - transmit_can_frame(&ECMP_0A6, can_config.battery); //Not in all logs - transmit_can_frame(&ECMP_37F, can_config.battery); //Seems to be temperatures of some sort - transmit_can_frame(&ECMP_372, can_config.battery); - transmit_can_frame(&ECMP_351, can_config.battery); - transmit_can_frame(&ECMP_31D, can_config.battery); + transmit_can_frame(&ECMP_010, can_config.battery); //VCU_BCM_Crash + if (simulateEntireCar) { + transmit_can_frame(&ECMP_31E, can_config.battery); + transmit_can_frame(&ECMP_383, can_config.battery); + transmit_can_frame(&ECMP_0A6, can_config.battery); //Not in all logs + transmit_can_frame(&ECMP_37F, can_config.battery); //Seems to be temperatures of some sort + transmit_can_frame(&ECMP_372, can_config.battery); + transmit_can_frame(&ECMP_351, can_config.battery); + transmit_can_frame(&ECMP_31D, can_config.battery); + } } // Send 500ms periodic CAN Message simulating the car still being attached if (currentMillis - previousMillis500 >= INTERVAL_500_MS) { previousMillis500 = currentMillis; - - transmit_can_frame(&ECMP_0AE, can_config.battery); + if (simulateEntireCar) { + transmit_can_frame(&ECMP_0AE, can_config.battery); + } } // Send 1s CAN Message if (currentMillis - previousMillis1000 >= INTERVAL_1_S) { @@ -1499,19 +1504,22 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) { ECMP_552.data.u8[2] = ((ticks_552 & 0x0000FF00) >> 8); ECMP_552.data.u8[3] = (ticks_552 & 0x000000FF); - transmit_can_frame(&ECMP_439, can_config.battery); //PSA Specific? Not in all logs - transmit_can_frame(&ECMP_486, can_config.battery); //Not in all logs - transmit_can_frame(&ECMP_041, can_config.battery); //Not in all logs - transmit_can_frame(&ECMP_786, can_config.battery); //Not in all logs - transmit_can_frame(&ECMP_591, can_config.battery); //Not in all logs + transmit_can_frame(&ECMP_439, can_config.battery); //OBC4 transmit_can_frame(&ECMP_552, can_config.battery); //VCU_552 timetracking - transmit_can_frame(&ECMP_794, can_config.battery); //Not in all logs + if (simulateEntireCar) { + transmit_can_frame(&ECMP_486, can_config.battery); //Not in all logs + transmit_can_frame(&ECMP_041, can_config.battery); //Not in all logs + transmit_can_frame(&ECMP_786, can_config.battery); //Not in all logs + transmit_can_frame(&ECMP_591, can_config.battery); //Not in all logs + transmit_can_frame(&ECMP_794, can_config.battery); //Not in all logs + } } // Send 5s periodic CAN Message simulating the car still being attached if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { previousMillis5000 = currentMillis; - - transmit_can_frame(&ECMP_55F, can_config.battery); + if (simulateEntireCar) { + transmit_can_frame(&ECMP_55F, can_config.battery); + } } } diff --git a/Software/src/battery/ECMP-BATTERY.h b/Software/src/battery/ECMP-BATTERY.h index c2065587..20c823e3 100644 --- a/Software/src/battery/ECMP-BATTERY.h +++ b/Software/src/battery/ECMP-BATTERY.h @@ -39,6 +39,8 @@ class EcmpBattery : public CanBattery { static const int MIN_CELL_VOLTAGE_MV = 2700; #define NOT_SAMPLED_YET 255 #define COMPLETED_STATE 0 + bool simulateEntireCar = + false; //Set this to true to simulate the whole car (useful for when using external diagnostic tools) bool battery_RelayOpenRequest = false; bool battery_InterlockOpen = false; uint8_t ContactorResetStatemachine = 0; @@ -217,7 +219,7 @@ class EcmpBattery : public CanBattery { uint16_t poll_state = PID_WELD_CHECK; uint16_t incoming_poll = 0; - CAN_frame ECMP_010 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x010, .data = {0xB4}}; + CAN_frame ECMP_010 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x010, .data = {0xB4}}; //VCU_BCM_Crash 100ms CAN_frame ECMP_041 = {.FD = false, .ext_ID = false, .DLC = 1, .ID = 0x041, .data = {0x00}}; CAN_frame ECMP_0A6 = {.FD = false, .ext_ID = false, @@ -333,7 +335,7 @@ class EcmpBattery : public CanBattery { .DLC = 8, .ID = 0x3A3, .data = {0x4A, 0x4A, 0x40, 0x00, 0x00, 0x08, 0x00, 0x0F}}; - CAN_frame ECMP_439 = {.FD = false, //??? 1s periodic (Perfectly emulated in Battery-Emulator) + CAN_frame ECMP_439 = {.FD = false, //OBC4 1s periodic (Perfectly emulated in Battery-Emulator) .ext_ID = false, //Same content always, fully static .DLC = 8, .ID = 0x439, From 532869c51e387746d1608fa3d19eb6a32fcbf0da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 15 Jun 2025 19:30:15 +0300 Subject: [PATCH 10/21] Correct scaling of polled values --- Software/src/battery/ECMP-BATTERY.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Software/src/battery/ECMP-BATTERY.cpp b/Software/src/battery/ECMP-BATTERY.cpp index 74207f88..8c9b8feb 100644 --- a/Software/src/battery/ECMP-BATTERY.cpp +++ b/Software/src/battery/ECMP-BATTERY.cpp @@ -534,10 +534,10 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) { pid_avg_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; break; case PID_CURRENT: - pid_current = (((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | - rx_frame.data.u8[7]) - - 76800) * - 10; + pid_current = -(((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | (rx_frame.data.u8[6] << 8) | + rx_frame.data.u8[7]) - + 76800) * + 20; break; case PID_INSULATION_NEG: pid_insulation_res_neg = ((rx_frame.data.u8[4] << 24) | (rx_frame.data.u8[5] << 16) | @@ -578,7 +578,7 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) { pid_lowest_cell_voltage_num = (rx_frame.data.u8[4]); break; case PID_SUM_OF_CELLS: - pid_sum_of_cells = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; + pid_sum_of_cells = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 2; break; case PID_CELL_MIN_CAPACITY: pid_cell_min_capacity = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]; From 0884acc72403a76e28a3b91ea103adb71159cb70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Sun, 15 Jun 2025 19:50:04 +0300 Subject: [PATCH 11/21] Improve startup to throw less events --- Software/src/battery/CHADEMO-BATTERY.cpp | 4 +++- Software/src/battery/CHADEMO-BATTERY.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index ff08d86e..a4ba76c8 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -21,7 +21,9 @@ void ChademoBattery::update_values() { datalayer.battery.status.max_discharge_power_W = (x200_discharge_limits.MaximumDischargeCurrent * x100_chg_lim.MaximumBatteryVoltage); //In Watts, Convert A to P - datalayer.battery.status.voltage_dV = get_measured_voltage() * 10; + if (vehicle_can_received) { // Only update the value sent towards inverter if vehicle is connected (avoids false positive events) + datalayer.battery.status.voltage_dV = get_measured_voltage() * 10; + } datalayer.battery.info.total_capacity_Wh = (x101_chg_est.RatedBatteryCapacity * 100); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version? diff --git a/Software/src/battery/CHADEMO-BATTERY.h b/Software/src/battery/CHADEMO-BATTERY.h index 7e50841f..29f1b3c6 100644 --- a/Software/src/battery/CHADEMO-BATTERY.h +++ b/Software/src/battery/CHADEMO-BATTERY.h @@ -127,7 +127,7 @@ uint8_t CHADEMO_seq = 0x0; } status; } s; - uint8_t StateOfCharge = 0; //6 state of charge? + uint8_t StateOfCharge = 50; //6 state of charge? }; /* ---------- CHARGING: EVSE Data structures */ From 57cb1186b8a2cc8de13d0a8b812cdace61ef7968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 17 Jun 2025 23:21:27 +0300 Subject: [PATCH 12/21] Update MinimumDischargeVoltage handling --- Software/src/battery/CHADEMO-BATTERY.cpp | 4 ++++ Software/src/battery/CHADEMO-BATTERY.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index a4ba76c8..0b6339d2 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -231,6 +231,10 @@ void ChademoBattery::process_vehicle_charging_limits(CAN_frame rx_frame) { if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) { #ifdef DEBUG_LOG logging.println("x200 minimum discharge voltage met or exceeded, stopping."); + logging.print("Measured: "); + logging.print(get_measured_voltage()); + logging.print("Minimum voltage: "); + logging.print(x200_discharge_limits.MinimumDischargeVoltage); #endif CHADEMO_Status = CHADEMO_STOP; } diff --git a/Software/src/battery/CHADEMO-BATTERY.h b/Software/src/battery/CHADEMO-BATTERY.h index 29f1b3c6..5f19df83 100644 --- a/Software/src/battery/CHADEMO-BATTERY.h +++ b/Software/src/battery/CHADEMO-BATTERY.h @@ -180,7 +180,7 @@ uint8_t CHADEMO_seq = 0x0; //H200 - Vehicle - Discharge limits struct x200_Vehicle_Discharge_Limits { uint8_t MaximumDischargeCurrent = 0xFF; - uint16_t MinimumDischargeVoltage = 0; + uint16_t MinimumDischargeVoltage = 260; //Initialized to a semi-sane value, updates via CAN later uint16_t MinimumBatteryDischargeLevel = 0; uint16_t MaxRemainingCapacityForCharging = 0; }; From 11280bc1ef67ed9f0c585a2bb04ae54e81ef4a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 17 Jun 2025 23:22:45 +0300 Subject: [PATCH 13/21] Update note on state --- Software/src/battery/CHADEMO-BATTERY.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 0b6339d2..833989cb 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -204,7 +204,7 @@ void ChademoBattery::process_vehicle_charging_session(CAN_frame rx_frame) { } #ifdef DEBUG_LOG - logging.println("UNHANDLED STATE IN process_vehicle_charging_session()"); + logging.println("UNHANDLED CHADEMO STATE, try unplugging chademo cable, reboot emulator, and retry!"); #endif return; } From b66934fb4289000cfb4bf6141593105f458efc63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 18 Jun 2025 14:51:50 +0300 Subject: [PATCH 14/21] Add improved statemachine for isolation disable --- Software/src/battery/ECMP-BATTERY.cpp | 38 +++++++++++++++++--------- Software/src/battery/ECMP-BATTERY.h | 6 +--- Software/src/devboard/utils/events.cpp | 3 ++ Software/src/devboard/utils/events.h | 1 + 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/Software/src/battery/ECMP-BATTERY.cpp b/Software/src/battery/ECMP-BATTERY.cpp index 8c9b8feb..54a5e55a 100644 --- a/Software/src/battery/ECMP-BATTERY.cpp +++ b/Software/src/battery/ECMP-BATTERY.cpp @@ -374,19 +374,31 @@ void EcmpBattery::handle_incoming_can_frame(CAN_frame rx_frame) { if (datalayer_extended.stellantisECMP.UserRequestDisableIsoMonitoring) { if ((rx_frame.data.u8[0] == 0x06) && (rx_frame.data.u8[1] == 0x50) && (rx_frame.data.u8[2] == 0x03)) { //06,50,03,00,C8,00,14,00, - DisableIsoMonitoringStatemachine = 2; //Send ECMP_FACTORY_MODE_ACTIVATION next loop + DisableIsoMonitoringStatemachine = 2; //Send ECMP_ACK_MESSAGE (02 3e 00) } - //if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F) && (rx_frame.data.u8[2] == 0x2E)) { - if (DisableIsoMonitoringStatemachine == 3) { - DisableIsoMonitoringStatemachine = 4; + if ((rx_frame.data.u8[0] == 0x02) && (rx_frame.data.u8[1] == 0x7E) && (rx_frame.data.u8[2] == 0x00)) { + //Expected 02,7E,00 + DisableIsoMonitoringStatemachine = 4; //Send ECMP_FACTORY_MODE_ACTIVATION next loop } - //if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F) && (rx_frame.data.u8[2] == 0x2E)) { - if (DisableIsoMonitoringStatemachine == 5) { - DisableIsoMonitoringStatemachine = 6; + if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x6E) && (rx_frame.data.u8[2] == 0xD9)) { + //Factory mode ENTRY: 2E.D9.00.01 + DisableIsoMonitoringStatemachine = 6; //Send ECMP_DISABLE_ISOLATION_REQ next loop } - //if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F) && (rx_frame.data.u8[2] == 0x31)) { - if (DisableIsoMonitoringStatemachine == 7) { - //UNKNOWN? 03,7F,31,24 (or 7F?) + if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F) && (rx_frame.data.u8[2] == 0x2E)) { + //Factory mode fails to enter with 7F + set_event(EVENT_PID_FAILED, rx_frame.data.u8[2]); + DisableIsoMonitoringStatemachine = + 6; //Send ECMP_DISABLE_ISOLATION_REQ next loop (pointless, since it will fail) + } + if ((rx_frame.data.u8[0] == 0x04) && (rx_frame.data.u8[1] == 0x31) && (rx_frame.data.u8[2] == 0x02)) { + //Disable isolation successful 04 31 02 df e1 + DisableIsoMonitoringStatemachine = COMPLETED_STATE; + datalayer_extended.stellantisECMP.UserRequestDisableIsoMonitoring = false; + timeSpentDisableIsoMonitoring = COMPLETED_STATE; + } + if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F) && (rx_frame.data.u8[2] == 0x31)) { + //Disable Isolation fails to enter with 7F + set_event(EVENT_PID_FAILED, rx_frame.data.u8[2]); DisableIsoMonitoringStatemachine = COMPLETED_STATE; datalayer_extended.stellantisECMP.UserRequestDisableIsoMonitoring = false; timeSpentDisableIsoMonitoring = COMPLETED_STATE; @@ -845,15 +857,15 @@ void EcmpBattery::transmit_can(unsigned long currentMillis) { DisableIsoMonitoringStatemachine = 1; } if (DisableIsoMonitoringStatemachine == 2) { - transmit_can_frame(&ECMP_FACTORY_MODE_ACTIVATION_NEW, can_config.battery); + transmit_can_frame(&ECMP_ACK_MESSAGE, can_config.battery); DisableIsoMonitoringStatemachine = 3; } if (DisableIsoMonitoringStatemachine == 4) { - transmit_can_frame(&ECMP_DISABLE_ISOLATION_REQ, can_config.battery); + transmit_can_frame(&ECMP_FACTORY_MODE_ACTIVATION, can_config.battery); DisableIsoMonitoringStatemachine = 5; } if (DisableIsoMonitoringStatemachine == 6) { - transmit_can_frame(&ECMP_FACTORY_MODE_ACTIVATION, can_config.battery); + transmit_can_frame(&ECMP_DISABLE_ISOLATION_REQ, can_config.battery); DisableIsoMonitoringStatemachine = 7; } timeSpentDisableIsoMonitoring++; diff --git a/Software/src/battery/ECMP-BATTERY.h b/Software/src/battery/ECMP-BATTERY.h index 20c823e3..64ef0f38 100644 --- a/Software/src/battery/ECMP-BATTERY.h +++ b/Software/src/battery/ECMP-BATTERY.h @@ -414,16 +414,12 @@ class EcmpBattery : public CanBattery { .DLC = 5, .ID = 0x6B4, .data = {0x04, 0x2E, 0xD9, 0x00, 0x01}}; - CAN_frame ECMP_FACTORY_MODE_ACTIVATION_NEW = {.FD = false, - .ext_ID = false, - .DLC = 4, - .ID = 0x6B4, - .data = {0x04, 0x2E, 0x19, 0x01}}; CAN_frame ECMP_DISABLE_ISOLATION_REQ = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6B4, .data = {0x04, 0x31, 0x02, 0xDF, 0xE1}}; + CAN_frame ECMP_ACK_MESSAGE = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x6B4, .data = {0x02, 0x3E, 0x00}}; uint8_t data_010_CRC[8] = {0xB4, 0x96, 0x78, 0x5A, 0x3C, 0x1E, 0xF0, 0xD2}; uint8_t data_3A2_CRC[16] = {0x0C, 0x1B, 0x2A, 0x39, 0x48, 0x57, 0x66, 0x75, 0x84, 0x93, 0xA2, 0xB1}; // NOTE. Changes on BMS state diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 6ea94c3f..8fce33b7 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -121,6 +121,7 @@ void init_events(void) { events.entries[EVENT_RJXZS_LOG].level = EVENT_LEVEL_INFO; events.entries[EVENT_PAUSE_BEGIN].level = EVENT_LEVEL_WARNING; events.entries[EVENT_PAUSE_END].level = EVENT_LEVEL_INFO; + events.entries[EVENT_PID_FAILED].level = EVENT_LEVEL_INFO; events.entries[EVENT_WIFI_CONNECT].level = EVENT_LEVEL_INFO; events.entries[EVENT_WIFI_DISCONNECT].level = EVENT_LEVEL_INFO; events.entries[EVENT_MQTT_CONNECT].level = EVENT_LEVEL_INFO; @@ -351,6 +352,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) { return "The emulator is trying to pause the battery."; case EVENT_PAUSE_END: return "The emulator is attempting to resume battery operation from pause."; + case EVENT_PID_FAILED: + return "Failed to write PID request to battery"; case EVENT_WIFI_CONNECT: return "Wifi connected."; case EVENT_WIFI_DISCONNECT: diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index c6b6ddc9..030c505b 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -95,6 +95,7 @@ XX(EVENT_RJXZS_LOG) \ XX(EVENT_PAUSE_BEGIN) \ XX(EVENT_PAUSE_END) \ + XX(EVENT_PID_FAILED) \ XX(EVENT_WIFI_CONNECT) \ XX(EVENT_WIFI_DISCONNECT) \ XX(EVENT_MQTT_CONNECT) \ From 447955acb2079f3f256a28ee7350445660f5d131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 19 Jun 2025 00:01:32 +0300 Subject: [PATCH 15/21] Make cellvoltage detection more reliable --- .../src/battery/KIA-HYUNDAI-64-BATTERY.cpp | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 0bf2368d..3e9d35ba 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -112,21 +112,18 @@ void KiaHyundai64Battery:: } void KiaHyundai64Battery::update_number_of_cells() { - //If we have cell values and number_of_cells not initialized yet - if (cellvoltages_mv[0] > 0 && datalayer_battery->info.number_of_cells == 0) { - // Check if we have 98S or 90S battery. If the 98th cell is valid range, we are on a 98S battery - if ((datalayer_battery->status.cell_voltages_mV[97] > 2000) && - (datalayer_battery->status.cell_voltages_mV[97] < 4300)) { - datalayer_battery->info.number_of_cells = 98; - datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; - datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_98S_DV; - datalayer_battery->info.total_capacity_Wh = 64000; - } else { - datalayer_battery->info.number_of_cells = 90; - datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_90S_DV; - datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; - datalayer_battery->info.total_capacity_Wh = 40000; - } + // Check if we have 98S or 90S battery. If the 98th cell is valid range, we are on a 98S battery + if ((datalayer_battery->status.cell_voltages_mV[97] > 2000) && + (datalayer_battery->status.cell_voltages_mV[97] < 4500)) { + datalayer_battery->info.number_of_cells = 98; + datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; + datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_98S_DV; + datalayer_battery->info.total_capacity_Wh = 64000; + } else { + datalayer_battery->info.number_of_cells = 90; + datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_90S_DV; + datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; + datalayer_battery->info.total_capacity_Wh = 40000; } } From 7e3eb74e0696a581efdd8f93a897249e5298d34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 19 Jun 2025 00:04:16 +0300 Subject: [PATCH 16/21] Add group check to sixth datarow --- .../src/battery/KIA-HYUNDAI-64-BATTERY.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 3e9d35ba..f2380559 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -362,16 +362,18 @@ void KiaHyundai64Battery::handle_incoming_can_frame(CAN_frame rx_frame) { } break; case 0x26: //Sixth datarow in PID group - //We have read all cells, check that content is valid: - for (uint8_t i = 85; i < 97; ++i) { - if (cellvoltages_mv[i] < 300) { // Zero the value if it's below 300 - cellvoltages_mv[i] = 0; // Some packs incorrectly report the last unpopulated cells as 20-60mV + if (poll_data_pid == 5) { + //We have read all cells, check that content is valid: + for (uint8_t i = 85; i < 97; ++i) { + if (cellvoltages_mv[i] < 300) { // Zero the value if it's below 300 + cellvoltages_mv[i] = 0; // Some packs incorrectly report the last unpopulated cells as 20-60mV + } } + //Map all cell voltages to the global array + memcpy(datalayer_battery->status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t)); + //Update number of cells + update_number_of_cells(); } - //Map all cell voltages to the global array - memcpy(datalayer_battery->status.cell_voltages_mV, cellvoltages_mv, 98 * sizeof(uint16_t)); - //Update number of cells - update_number_of_cells(); break; case 0x27: //Seventh datarow in PID group if (poll_data_pid == 1) { From 1525f8a26fb3b6c9db424be6bbb3d3cff3422d7a Mon Sep 17 00:00:00 2001 From: freddanastrom Date: Thu, 19 Jun 2025 21:18:18 +0200 Subject: [PATCH 17/21] Mapping unknown polls to support charged/discharged energy --- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 67 +++++++++++---------- Software/src/battery/BYD-ATTO-3-BATTERY.h | 9 +-- Software/src/battery/BYD-ATTO-3-HTML.h | 8 +-- Software/src/datalayer/datalayer_extended.h | 8 +-- 4 files changed, 48 insertions(+), 44 deletions(-) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index d8893204..edc4f85a 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -24,10 +24,10 @@ After battery has been unlocked, you can remove the "USE_ESTIMATED_SOC" from the #define POLL_MAX_CHARGE_POWER 0x000A #define UNKNOWN_POLL_3 0x000B //0x00B1 (177 interesting!) #define UNKNOWN_POLL_4 0x000E //0x0B27 (2855 interesting!) -#define UNKNOWN_POLL_5 0x000F //0x00237B (9083 interesting!) -#define UNKNOWN_POLL_6 0x0010 //0x00231B (8987 interesting!) -#define UNKNOWN_POLL_7 0x0011 //0x0E4E (3662 interesting!) -#define UNKNOWN_POLL_8 0x0012 //0x0E27 (3623 interesting) +#define POLL_TOTAL_CHARGED_AH 0x000F +#define POLL_TOTAL_DISCHARGED_AH 0x0010 +#define POLL_TOTAL_CHARGED_KWH 0x0011 +#define POLL_TOTAL_DISCHARGED_KWH 0x0012 #define UNKNOWN_POLL_9 0x0004 //0x0034 (52 interesting!) #define UNKNOWN_POLL_10 0x002A //0x5B #define UNKNOWN_POLL_11 0x002E //0x08 (probably module number, or cell number?) @@ -181,6 +181,9 @@ void BydAttoBattery:: datalayer_battery->status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV; + datalayer_battery->status.total_discharged_battery_Wh = BMS_total_discharged_kwh * 1000; + datalayer_battery->status.total_charged_battery_Wh = BMS_total_charged_kwh * 1000; + //Map all cell voltages to the global array memcpy(datalayer_battery->status.cell_voltages_mV, battery_cellvoltages, CELLCOUNT_EXTENDED * sizeof(uint16_t)); @@ -277,10 +280,10 @@ void BydAttoBattery:: datalayer_bydatto->chargePower = BMS_allowed_charge_power; datalayer_bydatto->unknown3 = BMS_unknown3; datalayer_bydatto->unknown4 = BMS_unknown4; - datalayer_bydatto->unknown5 = BMS_unknown5; - datalayer_bydatto->unknown6 = BMS_unknown6; - datalayer_bydatto->unknown7 = BMS_unknown7; - datalayer_bydatto->unknown8 = BMS_unknown8; + datalayer_bydatto->total_charged_ah = BMS_total_charged_ah; + datalayer_bydatto->total_discharged_ah = BMS_total_discharged_ah; + datalayer_bydatto->total_charged_kwh = BMS_total_charged_kwh; + datalayer_bydatto->total_discharged_kwh = BMS_total_discharged_kwh; datalayer_bydatto->unknown9 = BMS_unknown9; datalayer_bydatto->unknown10 = BMS_unknown10; datalayer_bydatto->unknown11 = BMS_unknown11; @@ -443,17 +446,17 @@ void BydAttoBattery::handle_incoming_can_frame(CAN_frame rx_frame) { case UNKNOWN_POLL_4: BMS_unknown4 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; break; - case UNKNOWN_POLL_5: - BMS_unknown5 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; + case POLL_TOTAL_CHARGED_AH: + BMS_total_charged_ah = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; break; - case UNKNOWN_POLL_6: - BMS_unknown6 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; + case POLL_TOTAL_DISCHARGED_AH: + BMS_total_discharged_ah = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; break; - case UNKNOWN_POLL_7: - BMS_unknown7 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; + case POLL_TOTAL_CHARGED_KWH: + BMS_total_charged_kwh = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; break; - case UNKNOWN_POLL_8: - BMS_unknown8 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; + case POLL_TOTAL_DISCHARGED_KWH: + BMS_total_discharged_kwh = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; break; case UNKNOWN_POLL_9: BMS_unknown9 = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]; @@ -623,26 +626,26 @@ void BydAttoBattery::transmit_can(unsigned long currentMillis) { case UNKNOWN_POLL_4: ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_4 & 0xFF00) >> 8); ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_4 & 0x00FF); - poll_state = UNKNOWN_POLL_5; + poll_state = POLL_TOTAL_CHARGED_AH; break; - case UNKNOWN_POLL_5: - ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_5 & 0xFF00) >> 8); - ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_5 & 0x00FF); - poll_state = UNKNOWN_POLL_6; + case POLL_TOTAL_CHARGED_AH: + ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_TOTAL_CHARGED_AH & 0xFF00) >> 8); + ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_TOTAL_CHARGED_AH & 0x00FF); + poll_state = POLL_TOTAL_DISCHARGED_AH; break; - case UNKNOWN_POLL_6: - ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_6 & 0xFF00) >> 8); - ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_6 & 0x00FF); - poll_state = UNKNOWN_POLL_7; + case POLL_TOTAL_DISCHARGED_AH: + ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_TOTAL_DISCHARGED_AH & 0xFF00) >> 8); + ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_TOTAL_DISCHARGED_AH & 0x00FF); + poll_state = POLL_TOTAL_CHARGED_KWH; break; - case UNKNOWN_POLL_7: - ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_7 & 0xFF00) >> 8); - ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_7 & 0x00FF); - poll_state = UNKNOWN_POLL_8; + case POLL_TOTAL_CHARGED_KWH: + ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_TOTAL_CHARGED_KWH & 0xFF00) >> 8); + ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_TOTAL_CHARGED_KWH & 0x00FF); + poll_state = POLL_TOTAL_DISCHARGED_KWH; break; - case UNKNOWN_POLL_8: - ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((UNKNOWN_POLL_8 & 0xFF00) >> 8); - ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(UNKNOWN_POLL_8 & 0x00FF); + case POLL_TOTAL_DISCHARGED_KWH: + ATTO_3_7E7_POLL.data.u8[2] = (uint8_t)((POLL_TOTAL_DISCHARGED_KWH & 0xFF00) >> 8); + ATTO_3_7E7_POLL.data.u8[3] = (uint8_t)(POLL_TOTAL_DISCHARGED_KWH & 0x00FF); poll_state = UNKNOWN_POLL_9; break; case UNKNOWN_POLL_9: diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.h b/Software/src/battery/BYD-ATTO-3-BATTERY.h index a5aa9043..a3621863 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.h +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.h @@ -53,6 +53,7 @@ class BydAttoBattery : public CanBattery { virtual void update_values(); virtual void transmit_can(unsigned long currentMillis); + bool supports_charged_energy() { return true; } bool supports_reset_crash() { return true; } void reset_crash() { datalayer_bydatto->UserRequestCrashReset = true; } @@ -114,10 +115,10 @@ class BydAttoBattery : public CanBattery { uint16_t BMS_allowed_charge_power = 0; uint16_t BMS_unknown3 = 0; uint16_t BMS_unknown4 = 0; - uint16_t BMS_unknown5 = 0; - uint16_t BMS_unknown6 = 0; - uint16_t BMS_unknown7 = 0; - uint16_t BMS_unknown8 = 0; + uint16_t BMS_total_charged_ah = 0; + uint16_t BMS_total_discharged_ah = 0; + uint16_t BMS_total_charged_kwh = 0; + uint16_t BMS_total_discharged_kwh = 0; uint16_t BMS_unknown9 = 0; uint8_t BMS_unknown10 = 0; uint8_t BMS_unknown11 = 0; diff --git a/Software/src/battery/BYD-ATTO-3-HTML.h b/Software/src/battery/BYD-ATTO-3-HTML.h index b133db84..2a344522 100644 --- a/Software/src/battery/BYD-ATTO-3-HTML.h +++ b/Software/src/battery/BYD-ATTO-3-HTML.h @@ -34,10 +34,10 @@ class BydAtto3HtmlRenderer : public BatteryHtmlRenderer { content += "

Charge power raw: " + String(byd_datalayer->chargePower) + "

"; content += "

Unknown3: " + String(byd_datalayer->unknown3) + "

"; content += "

Unknown4: " + String(byd_datalayer->unknown4) + "

"; - content += "

Unknown5: " + String(byd_datalayer->unknown5) + "

"; - content += "

Unknown6: " + String(byd_datalayer->unknown6) + "

"; - content += "

Unknown7: " + String(byd_datalayer->unknown7) + "

"; - content += "

Unknown8: " + String(byd_datalayer->unknown8) + "

"; + content += "

Total charged Ah: " + String(byd_datalayer->total_charged_ah) + "

"; + content += "

Total discharged Ah: " + String(byd_datalayer->total_discharged_ah) + "

"; + content += "

Total charged kWh: " + String(byd_datalayer->total_charged_kwh) + "

"; + content += "

Total discharged kWh: " + String(byd_datalayer->total_discharged_kwh) + "

"; content += "

Unknown9: " + String(byd_datalayer->unknown9) + "

"; content += "

Unknown10: " + String(byd_datalayer->unknown10) + "

"; content += "

Unknown11: " + String(byd_datalayer->unknown11) + "

"; diff --git a/Software/src/datalayer/datalayer_extended.h b/Software/src/datalayer/datalayer_extended.h index b9456887..9c10deab 100644 --- a/Software/src/datalayer/datalayer_extended.h +++ b/Software/src/datalayer/datalayer_extended.h @@ -197,10 +197,10 @@ typedef struct { uint16_t chargePower = 0; uint16_t unknown3 = 0; uint16_t unknown4 = 0; - uint16_t unknown5 = 0; - uint16_t unknown6 = 0; - uint16_t unknown7 = 0; - uint16_t unknown8 = 0; + uint16_t total_charged_ah = 0; + uint16_t total_discharged_ah = 0; + uint16_t total_charged_kwh = 0; + uint16_t total_discharged_kwh = 0; uint16_t unknown9 = 0; uint8_t unknown10 = 0; uint8_t unknown11 = 0; From 44cdedb6310838a289f67a419ae12d1fa78e2081 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 19:55:51 +0000 Subject: [PATCH 18/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Software/src/battery/BYD-ATTO-3-BATTERY.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp index edc4f85a..e38a3f25 100644 --- a/Software/src/battery/BYD-ATTO-3-BATTERY.cpp +++ b/Software/src/battery/BYD-ATTO-3-BATTERY.cpp @@ -22,8 +22,8 @@ After battery has been unlocked, you can remove the "USE_ESTIMATED_SOC" from the #define UNKNOWN_POLL_0 0x1FFE //0x64 19 C4 3B #define UNKNOWN_POLL_1 0x1FFC //0x72 1F C4 3B #define POLL_MAX_CHARGE_POWER 0x000A -#define UNKNOWN_POLL_3 0x000B //0x00B1 (177 interesting!) -#define UNKNOWN_POLL_4 0x000E //0x0B27 (2855 interesting!) +#define UNKNOWN_POLL_3 0x000B //0x00B1 (177 interesting!) +#define UNKNOWN_POLL_4 0x000E //0x0B27 (2855 interesting!) #define POLL_TOTAL_CHARGED_AH 0x000F #define POLL_TOTAL_DISCHARGED_AH 0x0010 #define POLL_TOTAL_CHARGED_KWH 0x0011 From d4aaa2911dcfc6db1f6a349c74987945518e15ed Mon Sep 17 00:00:00 2001 From: Jaakko Haakana Date: Mon, 23 Jun 2025 20:23:37 +0300 Subject: [PATCH 19/21] Fix null-pointer reference --- Software/src/battery/BMW-I3-BATTERY.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Software/src/battery/BMW-I3-BATTERY.h b/Software/src/battery/BMW-I3-BATTERY.h index 9a60a0ce..b1b34d5f 100644 --- a/Software/src/battery/BMW-I3-BATTERY.h +++ b/Software/src/battery/BMW-I3-BATTERY.h @@ -20,7 +20,6 @@ class BmwI3Battery : public CanBattery { contactor_closing_allowed = contactor_closing_allowed_ptr; allows_contactor_closing = nullptr; wakeup_pin = wakeup; - *allows_contactor_closing = true; //Init voltage to 0 to allow contactor check to operate without fear of default values colliding battery_volts = 0; From 786b7a4fcc99dee4a7ae3bd2aa4539f793d3f5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Thu, 26 Jun 2025 23:20:38 +0300 Subject: [PATCH 20/21] Update USER_SETTINGS.h note Update note on how BATTERY_WH_MAX works --- Software/USER_SETTINGS.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 57b92625..b888f4b8 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -139,7 +139,7 @@ #define HA_AUTODISCOVERY // Enable this line to send Home Assistant autodiscovery messages. If not enabled manual configuration of Home Assitant is required /* Battery settings */ -// Predefined total energy capacity of the battery in Watt-hours +// Predefined total energy capacity of the battery in Watt-hours (updates automatically from battery data when available) #define BATTERY_WH_MAX 30000 // Increases battery life. If true will rescale SOC between the configured min/max-percentage #define BATTERY_USE_SCALED_SOC true From 6e9e7605cc26e5365c39700dfb23ad9f60d4d7b4 Mon Sep 17 00:00:00 2001 From: Jaakko Haakana Date: Fri, 27 Jun 2025 21:57:57 +0300 Subject: [PATCH 21/21] Fix Volvo Spa info page by adding missing renderer. --- Software/src/battery/VOLVO-SPA-BATTERY.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Software/src/battery/VOLVO-SPA-BATTERY.h b/Software/src/battery/VOLVO-SPA-BATTERY.h index 4ea009ed..a1becbf4 100644 --- a/Software/src/battery/VOLVO-SPA-BATTERY.h +++ b/Software/src/battery/VOLVO-SPA-BATTERY.h @@ -25,7 +25,11 @@ class VolvoSpaBattery : public CanBattery { bool supports_reset_BECM() { return true; } void reset_BECM() { datalayer_extended.VolvoPolestar.UserRequestBECMecuReset = true; } + BatteryHtmlRenderer& get_status_renderer() { return renderer; } + private: + VolvoSpaHtmlRenderer renderer; + void readCellVoltages(); static const int MAX_PACK_VOLTAGE_108S_DV = 4540;