From d715d169fe50dae9ca996d431168626d22cf9236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 19 Aug 2025 21:27:29 +0300 Subject: [PATCH 01/16] Avoid oscillation in precharge incase it fails/timeoutus --- .../precharge_control/precharge_control.cpp | 14 ++++++++++++-- Software/src/devboard/utils/types.h | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Software/src/communication/precharge_control/precharge_control.cpp b/Software/src/communication/precharge_control/precharge_control.cpp index efdf3ff9..3557c95e 100644 --- a/Software/src/communication/precharge_control/precharge_control.cpp +++ b/Software/src/communication/precharge_control/precharge_control.cpp @@ -64,6 +64,14 @@ void handle_precharge_control(unsigned long currentMillis) { auto hia4v1_pin = esp32hal->HIA4V1_PIN(); auto inverter_disconnect_contactor_pin = esp32hal->INVERTER_DISCONNECT_CONTACTOR_PIN(); + // If we're in FAILURE state, completely disable any further precharge attempts + if (datalayer.system.status.precharge_status == AUTO_PRECHARGE_FAILURE) { + pinMode(hia4v1_pin, OUTPUT); + digitalWrite(hia4v1_pin, LOW); + digitalWrite(inverter_disconnect_contactor_pin, ON); + return; // Exit immediately - no further processing allowed. Reboot required to recover + } + int32_t target_voltage = datalayer.battery.status.voltage_dV; int32_t external_voltage = datalayer_extended.meb.BMS_voltage_intermediate_dV; @@ -128,11 +136,13 @@ void handle_precharge_control(unsigned long currentMillis) { pinMode(hia4v1_pin, OUTPUT); digitalWrite(hia4v1_pin, LOW); digitalWrite(inverter_disconnect_contactor_pin, ON); - datalayer.system.status.precharge_status = AUTO_PRECHARGE_OFF; + datalayer.system.status.precharge_status = AUTO_PRECHARGE_FAILURE; #ifdef DEBUG_LOG - logging.printf("Precharge: Disabled (timeout reached / BMS fault) -> AUTO_PRECHARGE_OFF\n"); + logging.printf("Precharge: CRITICAL FAILURE (timeout/BMS fault) -> REQUIRES REBOOT\n"); #endif set_event(EVENT_AUTOMATIC_PRECHARGE_FAILURE, 0); + // Force stop any further precharge attempts + datalayer.system.settings.start_precharging = false; // Add event } else if (datalayer.system.status.battery_allows_contactor_closing) { diff --git a/Software/src/devboard/utils/types.h b/Software/src/devboard/utils/types.h index d1e15340..577241e8 100644 --- a/Software/src/devboard/utils/types.h +++ b/Software/src/devboard/utils/types.h @@ -28,7 +28,8 @@ enum PrechargeState { AUTO_PRECHARGE_START, AUTO_PRECHARGE_PRECHARGING, AUTO_PRECHARGE_OFF, - AUTO_PRECHARGE_COMPLETED + AUTO_PRECHARGE_COMPLETED, + AUTO_PRECHARGE_FAILURE }; #define DISCHARGING 1 From d20712f3014f4dc7b6e4f9b04bf22379d535c584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Tue, 19 Aug 2025 21:30:18 +0300 Subject: [PATCH 02/16] Update event for precharge failure --- Software/src/devboard/utils/events.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index b5ba4a8f..32793827 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -262,7 +262,7 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) { case EVENT_PRECHARGE_FAILURE: return "Battery failed to precharge. Check that capacitor is seated on high voltage output."; case EVENT_AUTOMATIC_PRECHARGE_FAILURE: - return "Automatic precharge failed to reach target voltae."; + return "Automatic precharge FAILURE. Failed to reach target voltage or BMS timeout. Reboot emulator to retry!"; case EVENT_INTERNAL_OPEN_FAULT: return "High voltage cable removed while battery running. Opening contactors!"; case EVENT_INVERTER_OPEN_CONTACTOR: From 37f9d3e8f8c5b6bff6788c200e9a0ac123f06cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 20 Aug 2025 12:58:45 +0300 Subject: [PATCH 03/16] Add Relion battery protocol --- Software/USER_SETTINGS.h | 1 + Software/src/battery/BATTERIES.cpp | 4 ++ Software/src/battery/BATTERIES.h | 1 + Software/src/battery/Battery.h | 1 + Software/src/battery/RELION-LV-BATTERY.cpp | 83 ++++++++++++++++++++++ Software/src/battery/RELION-LV-BATTERY.h | 43 +++++++++++ 6 files changed, 133 insertions(+) create mode 100644 Software/src/battery/RELION-LV-BATTERY.cpp create mode 100644 Software/src/battery/RELION-LV-BATTERY.h diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index a1d246fa..2dccf642 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -35,6 +35,7 @@ //#define DALY_BMS //#define RJXZS_BMS //#define RANGE_ROVER_PHEV_BATTERY +//#define RELION_BATTERY //#define RENAULT_KANGOO_BATTERY //#define RENAULT_TWIZY_BATTERY //#define RENAULT_ZOE_GEN1_BATTERY diff --git a/Software/src/battery/BATTERIES.cpp b/Software/src/battery/BATTERIES.cpp index 1aa07bc6..9b81a479 100644 --- a/Software/src/battery/BATTERIES.cpp +++ b/Software/src/battery/BATTERIES.cpp @@ -108,6 +108,8 @@ const char* name_for_battery_type(BatteryType type) { return RjxzsBms::Name; case BatteryType::RangeRoverPhev: return RangeRoverPhevBattery::Name; + case BatteryType::RelionBattery: + return RelionBattery::Name; case BatteryType::RenaultKangoo: return RenaultKangooBattery::Name; case BatteryType::RenaultTwizy: @@ -209,6 +211,8 @@ Battery* create_battery(BatteryType type) { return new RjxzsBms(); case BatteryType::RangeRoverPhev: return new RangeRoverPhevBattery(); + case BatteryType::RelionBattery: + return new RelionBattery(); case BatteryType::RenaultKangoo: return new RenaultKangooBattery(); case BatteryType::RenaultTwizy: diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index 3e89149c..2c649a47 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -39,6 +39,7 @@ void setup_can_shunt(); #include "ORION-BMS.h" #include "PYLON-BATTERY.h" #include "RANGE-ROVER-PHEV-BATTERY.h" +#include "RELION-LV-BATTERY.h" #include "RENAULT-KANGOO-BATTERY.h" #include "RENAULT-TWIZY.h" #include "RENAULT-ZOE-GEN1-BATTERY.h" diff --git a/Software/src/battery/Battery.h b/Software/src/battery/Battery.h index 2b163a95..da98a513 100644 --- a/Software/src/battery/Battery.h +++ b/Software/src/battery/Battery.h @@ -45,6 +45,7 @@ enum class BatteryType { MgHsPhev = 37, SamsungSdiLv = 38, HyundaiIoniq28 = 39, + RelionBattery = 40, Highest }; diff --git a/Software/src/battery/RELION-LV-BATTERY.cpp b/Software/src/battery/RELION-LV-BATTERY.cpp new file mode 100644 index 00000000..693a5f8f --- /dev/null +++ b/Software/src/battery/RELION-LV-BATTERY.cpp @@ -0,0 +1,83 @@ +#include "RELION-LV-BATTERY.h" +#include "../battery/BATTERIES.h" +#include "../communication/can/comm_can.h" +#include "../datalayer/datalayer.h" +#include "../devboard/utils/events.h" +/*CAN Type:CAN2.0(Extended) +BPS:250kbps +Data Length: 8 +Data Encoded Format:Motorola*/ + +void RelionBattery::update_values() { + + datalayer.battery.status.real_soc = battery_soc * 100; + + 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.soh_pptt = battery_soh * 100; + + datalayer.battery.status.voltage_dV = battery_total_voltage; + + datalayer.battery.status.current_dA = battery_total_current; //Charging negative, discharge positive + + datalayer.battery.status.max_charge_power_W = + ((battery_total_voltage / 10) * charge_current_A); //90A recommended charge current + + datalayer.battery.status.max_discharge_power_W = + ((battery_total_voltage / 10) * discharge_current_A); //150A max continous discharge current + + datalayer.battery.status.temperature_min_dC = max_cell_temperature * 10; + + datalayer.battery.status.temperature_max_dC = max_cell_temperature * 10; + + datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; + + datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; +} + +void RelionBattery::handle_incoming_can_frame(CAN_frame rx_frame) { + switch (rx_frame.ID) { + case 0x02018100: //ID1 + battery_total_voltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); + break; + case 0x02028100: //ID2 + battery_total_current = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]); + system_state = rx_frame.data.u8[2]; + battery_soc = rx_frame.data.u8[3]; + battery_soh = rx_frame.data.u8[4]; + most_serious_fault = rx_frame.data.u8[5]; + break; + case 0x02038100: //ID3 + max_cell_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]); + min_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + break; + case 0x02628100: //Temperatures + max_cell_temperature = rx_frame.data.u8[0] - 50; + min_cell_temperature = rx_frame.data.u8[2] - 50; + break; + case 0x02648100: //Charging limitis + charge_current_A = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) - 800; + regen_charge_current_A = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) - 800; + discharge_current_A = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) - 800; + break; + default: + break; + } +} + +void RelionBattery::transmit_can(unsigned long currentMillis) { + // No periodic sending for this protocol +} + +void RelionBattery::setup(void) { // Performs one time setup at startup + strncpy(datalayer.system.info.battery_protocol, Name, 63); + datalayer.system.info.battery_protocol[63] = '\0'; + datalayer.battery.info.chemistry = LFP; + datalayer.battery.info.number_of_cells = 16; + 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.system.status.battery_allows_contactor_closing = true; +} diff --git a/Software/src/battery/RELION-LV-BATTERY.h b/Software/src/battery/RELION-LV-BATTERY.h new file mode 100644 index 00000000..53c59431 --- /dev/null +++ b/Software/src/battery/RELION-LV-BATTERY.h @@ -0,0 +1,43 @@ +#ifndef RELION_BATTERY_H +#define RELION_BATTERY_H + +#include "../system_settings.h" +#include "CanBattery.h" + +#ifdef RELION_BATTERY +#define SELECTED_BATTERY_CLASS RelionBattery +#endif + +class RelionBattery : public CanBattery { + public: + RelionBattery() : CanBattery(CAN_Speed::CAN_SPEED_250KBPS) {} + + virtual void setup(void); + virtual void handle_incoming_can_frame(CAN_frame rx_frame); + virtual void update_values(); + virtual void transmit_can(unsigned long currentMillis); + static constexpr const char* Name = "Relion LV protocol via 250kbps CAN"; + + private: + static const int MAX_PACK_VOLTAGE_DV = 584; //58.4V recommended charge voltage. BMS protection steps in at 60.8V + static const int MIN_PACK_VOLTAGE_DV = 440; //44.0V Recommended LV disconnect. BMS protection steps in at 40.0V + static const int MAX_CELL_DEVIATION_MV = 300; + static const int MAX_CELL_VOLTAGE_MV = 3800; //Battery is put into emergency stop if one cell goes over this value + static const int MIN_CELL_VOLTAGE_MV = 2700; //Battery is put into emergency stop if one cell goes below this value + + uint16_t battery_total_voltage = 500; + int16_t battery_total_current = 0; + uint8_t system_state = 0; + uint8_t battery_soc = 50; + uint8_t battery_soh = 99; + uint8_t most_serious_fault = 0; + uint16_t max_cell_voltage = 3300; + uint16_t min_cell_voltage = 3300; + int16_t max_cell_temperature = 0; + int16_t min_cell_temperature = 0; + int16_t charge_current_A = 0; + int16_t regen_charge_current_A = 0; + int16_t discharge_current_A = 0; +}; + +#endif From c988245d5f3c954a6cf482ddfab5da64ec62db50 Mon Sep 17 00:00:00 2001 From: mbuhansen <91631350+mbuhansen@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:37:09 +0200 Subject: [PATCH 04/16] Update KOSTAL-RS485.cpp Add contactor test cycle. --- Software/src/inverter/KOSTAL-RS485.cpp | 56 ++++++++++++++++---------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index ef5dfc6b..94bf8729 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -154,24 +154,18 @@ void KostalInverterProtocol::update_values() { float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 18); // Last current float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 22); // Should be Avg current(1s) - // Close contactors after 7 battery info frames requested - if (f2_startup_count > 7) { + // Close contactors after 20 battery info frames requested + if (f2_startup_count > 20) { datalayer.system.status.inverter_allows_contactor_closing = true; - dbg_message("inverter_allows_contactor_closing -> true"); + dbg_message("inverter_allows_contactor_closing -> true (info frame)"); } - // On startup, byte 56 seems to be always 0x00 couple of frames,. - if (f2_startup_count < 9) { - CYCLIC_DATA[56] = 0x00; - } else { + if (datalayer.system.status.inverter_allows_contactor_closing) { CYCLIC_DATA[56] = 0x01; - } - - // On startup, byte 59 seems to be always 0x02 couple of frames,. - if (f2_startup_count < 14) { - CYCLIC_DATA[59] = 0x02; - } else { CYCLIC_DATA[59] = 0x00; + } else { + CYCLIC_DATA[56] = 0x00; + CYCLIC_DATA[59] = 0x02; } #endif @@ -214,6 +208,12 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th { currentMillis = millis(); + // Auto-reset contactor_test_active after 5 seconds + if (contactortestTimerActive && (millis() - contactortestTimerStart >= 5000)) { + datalayer.system.status.inverter_allows_contactor_closing = true; + dbg_message("inverter_allows_contactor_closing -> true (Contactor test ended)"); + contactortestTimerActive = false; + } if (datalayer.system.status.battery_allows_contactor_closing & !contactorMillis) { contactorMillis = currentMillis; } @@ -232,7 +232,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th if (check_kostal_frame_crc(rx_index)) { incoming_message_counter = RS485_HEALTHY; - if (RS485_RXFRAME[1] == 'c' && info_sent) { + if (RS485_RXFRAME[1] == 0x63) { if (RS485_RXFRAME[6] == 0x47) { // Set time function - Do nothing. send_kostal(ACK_FRAME, 8); // ACK @@ -240,11 +240,20 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th if (RS485_RXFRAME[6] == 0x5E) { // Set State function if (RS485_RXFRAME[7] == 0x00) { + // Allow contactor closing + datalayer.system.status.inverter_allows_contactor_closing = true; + dbg_message("inverter_allows_contactor_closing -> true (5E 02)"); send_kostal(ACK_FRAME, 8); // ACK } else if (RS485_RXFRAME[7] == 0x04) { + // contactor test STATE, ACK sent + datalayer.system.status.inverter_allows_contactor_closing = false; + dbg_message("inverter_allows_contactor_closing -> false (Contactor test start)"); + send_kostal(ACK_FRAME, 8); // ACK + contactortestTimerStart = millis(); + contactortestTimerActive = true; + } else if (RS485_RXFRAME[7] == 0x00) { + // ERROR STATE, ACK sent send_kostal(ACK_FRAME, 8); // ACK - } else if (RS485_RXFRAME[7] == 0xFF) { - // no ACK sent } else { // Battery deep sleep? send_kostal(ACK_FRAME, 8); // ACK @@ -255,7 +264,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th //Reverse polarity, do nothing } else { int code = RS485_RXFRAME[6] + RS485_RXFRAME[7] * 0x100; - if (code == 0x44a && info_sent) { + if (code == 0x44a) { //Send cyclic data // TODO: Probably not a good idea to use the battery object here like this. if (battery) { @@ -265,7 +274,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th if (f2_startup_count < 15) { f2_startup_count++; } - uint8_t tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation + byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, CYCLIC_DATA, 64); tmpframe[62] = calculate_kostal_crc(tmpframe, 62); null_stuffer(tmpframe, 64); @@ -274,19 +283,22 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th } if (code == 0x84a) { //Send battery info - uint8_t tmpframe[40]; //copy values to prevent data manipulation during rewrite/crc calculation + byte tmpframe[40]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, BATTERY_INFO, 40); tmpframe[38] = calculate_kostal_crc(tmpframe, 38); null_stuffer(tmpframe, 40); send_kostal(tmpframe, 40); - info_sent = true; + datalayer.system.status.inverter_allows_contactor_closing = false; + dbg_message("inverter_allows_contactor_closing -> false (battery info sent)"); + + // Kun første gang sættes startupMillis if (!startupMillis) { startupMillis = currentMillis; } } - if (code == 0x353 && info_sent) { + if (code == 0x353) { //Send battery error/status - uint8_t tmpframe[9]; //copy values to prevent data manipulation during rewrite/crc calculation + byte tmpframe[9]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, STATUS_FRAME, 9); tmpframe[7] = calculate_kostal_crc(tmpframe, 7); null_stuffer(tmpframe, 9); From 6645f3c1f3c572214c7056ce671a941569f98c2a Mon Sep 17 00:00:00 2001 From: mbuhansen <91631350+mbuhansen@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:38:02 +0200 Subject: [PATCH 05/16] Update KOSTAL-RS485.h --- Software/src/inverter/KOSTAL-RS485.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Software/src/inverter/KOSTAL-RS485.h b/Software/src/inverter/KOSTAL-RS485.h index a14d5dca..19c4137d 100644 --- a/Software/src/inverter/KOSTAL-RS485.h +++ b/Software/src/inverter/KOSTAL-RS485.h @@ -43,6 +43,8 @@ class KostalInverterProtocol : public Rs485InverterProtocol { unsigned long currentMillis; unsigned long startupMillis = 0; unsigned long contactorMillis = 0; + unsigned long contactortestTimerStart = 0; + bool contactortestTimerActive = false; uint16_t rx_index = 0; bool RX_allow = false; From 93556780eb915578315ad9525ae7530214157337 Mon Sep 17 00:00:00 2001 From: mbuhansen <91631350+mbuhansen@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:49:21 +0200 Subject: [PATCH 06/16] Update KOSTAL-RS485.cpp --- Software/src/inverter/KOSTAL-RS485.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 94bf8729..4bc32e92 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -254,6 +254,8 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th } else if (RS485_RXFRAME[7] == 0x00) { // ERROR STATE, ACK sent send_kostal(ACK_FRAME, 8); // ACK + } else if (RS485_RXFRAME[7] == 0xFF) { + // no ACK sent } else { // Battery deep sleep? send_kostal(ACK_FRAME, 8); // ACK From 56bfcbe66410f8465b38efa8fb7d67d52fa2ffe5 Mon Sep 17 00:00:00 2001 From: mbuhansen <91631350+mbuhansen@users.noreply.github.com> Date: Wed, 20 Aug 2025 21:01:54 +0200 Subject: [PATCH 07/16] Update KOSTAL-RS485.cpp --- Software/src/inverter/KOSTAL-RS485.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 4bc32e92..eb982f20 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -232,7 +232,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th if (check_kostal_frame_crc(rx_index)) { incoming_message_counter = RS485_HEALTHY; - if (RS485_RXFRAME[1] == 0x63) { + if (RS485_RXFRAME[1] == 'c' && info_sent) { if (RS485_RXFRAME[6] == 0x47) { // Set time function - Do nothing. send_kostal(ACK_FRAME, 8); // ACK @@ -251,9 +251,6 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th send_kostal(ACK_FRAME, 8); // ACK contactortestTimerStart = millis(); contactortestTimerActive = true; - } else if (RS485_RXFRAME[7] == 0x00) { - // ERROR STATE, ACK sent - send_kostal(ACK_FRAME, 8); // ACK } else if (RS485_RXFRAME[7] == 0xFF) { // no ACK sent } else { @@ -266,7 +263,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th //Reverse polarity, do nothing } else { int code = RS485_RXFRAME[6] + RS485_RXFRAME[7] * 0x100; - if (code == 0x44a) { + if (code == 0x44a && info_sent) { //Send cyclic data // TODO: Probably not a good idea to use the battery object here like this. if (battery) { @@ -276,7 +273,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th if (f2_startup_count < 15) { f2_startup_count++; } - byte tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation + uint8_t tmpframe[64]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, CYCLIC_DATA, 64); tmpframe[62] = calculate_kostal_crc(tmpframe, 62); null_stuffer(tmpframe, 64); @@ -285,7 +282,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th } if (code == 0x84a) { //Send battery info - byte tmpframe[40]; //copy values to prevent data manipulation during rewrite/crc calculation + uint8_t tmpframe[40]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, BATTERY_INFO, 40); tmpframe[38] = calculate_kostal_crc(tmpframe, 38); null_stuffer(tmpframe, 40); @@ -300,7 +297,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th } if (code == 0x353) { //Send battery error/status - byte tmpframe[9]; //copy values to prevent data manipulation during rewrite/crc calculation + uint8_t tmpframe[9]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, STATUS_FRAME, 9); tmpframe[7] = calculate_kostal_crc(tmpframe, 7); null_stuffer(tmpframe, 9); From b8059de996d2bc36d568293eeba1ece34f31c780 Mon Sep 17 00:00:00 2001 From: mbuhansen <91631350+mbuhansen@users.noreply.github.com> Date: Wed, 20 Aug 2025 21:15:06 +0200 Subject: [PATCH 08/16] Update KOSTAL-RS485.cpp --- Software/src/inverter/KOSTAL-RS485.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index eb982f20..62ee8169 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -289,13 +289,12 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th send_kostal(tmpframe, 40); datalayer.system.status.inverter_allows_contactor_closing = false; dbg_message("inverter_allows_contactor_closing -> false (battery info sent)"); - - // Kun første gang sættes startupMillis + info_sent = true; if (!startupMillis) { startupMillis = currentMillis; } } - if (code == 0x353) { + if (code == 0x353 && info_sent) { //Send battery error/status uint8_t tmpframe[9]; //copy values to prevent data manipulation during rewrite/crc calculation memcpy(tmpframe, STATUS_FRAME, 9); From 38e900bed7322c98d7a92529ac81610722fb3085 Mon Sep 17 00:00:00 2001 From: James Brookes Date: Sun, 24 Aug 2025 15:05:44 +0100 Subject: [PATCH 09/16] Add pack SOC reset feature, fix BMS reset bug --- Software/src/battery/Battery.h | 2 + Software/src/battery/TESLA-BATTERY.cpp | 59 ++++++++++++++++++- Software/src/battery/TESLA-BATTERY.h | 4 ++ Software/src/datalayer/datalayer.h | 1 + .../webserver/advanced_battery_html.cpp | 4 ++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/Software/src/battery/Battery.h b/Software/src/battery/Battery.h index 2b163a95..45dd1919 100644 --- a/Software/src/battery/Battery.h +++ b/Software/src/battery/Battery.h @@ -73,6 +73,7 @@ class Battery { virtual bool supports_clear_isolation() { return false; } virtual bool supports_reset_BMS() { return false; } + virtual bool supports_reset_SOC() { return false; } virtual bool supports_reset_crash() { return false; } virtual bool supports_reset_NVROL() { return false; } virtual bool supports_reset_DTC() { return false; } @@ -91,6 +92,7 @@ class Battery { virtual void clear_isolation() {} virtual void reset_BMS() {} + virtual void reset_SOC() {} virtual void reset_crash() {} virtual void reset_contactor() {} virtual void reset_NVROL() {} diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 32599f93..3a0f49e0 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -727,10 +727,26 @@ void TeslaBattery:: #ifdef DEBUG_LOG logging.println("ERROR: BMS reset failed due to contactors not being open, or BMS ECU not allowing it"); #endif //DEBUG_LOG - stateMachineBMSReset = 0; + stateMachineBMSReset = 0xFF; datalayer.battery.settings.user_requests_tesla_bms_reset = false; } } + if (datalayer.battery.settings.user_requests_tesla_soc_reset) { + if (datalayer.battery.status.real_soc < 1500 || datalayer.battery.status.real_soc > 9000) { + //Start the SOC reset statemachine, only if SOC < 15% or > 90% + stateMachineSOCReset = 0; + datalayer.battery.settings.user_requests_tesla_soc_reset = false; +#ifdef DEBUG_LOG + logging.println("SOC reset requested"); +#endif //DEBUG_LOG + } else { +#ifdef DEBUG_LOG + logging.println("ERROR: SOC reset failed due to SOC not being less than 15 or greater than 90"); +#endif //DEBUG_LOG + stateMachineSOCReset = 0xFF; + datalayer.battery.settings.user_requests_tesla_soc_reset = false; + } + } //Update 0x333 UI_chargeTerminationPct (bit 16, width 10) value to SOC max value - expose via UI? //One firmware version this was seen at bit 17 width 11 @@ -2312,6 +2328,47 @@ void TeslaBattery::transmit_can(unsigned long currentMillis) { break; } } + if (stateMachineSOCReset != 0xFF) { + //This implementation should be rewritten to actually reply to the UDS responses sent by the BMS + //While this may work, it is not the correct way to implement this + switch (stateMachineBMSReset) { + case 0: + TESLA_602.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602); + stateMachineBMSReset = 1; + break; + case 1: + TESLA_602.data = {0x30, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602); + stateMachineBMSReset = 2; + break; + case 2: + TESLA_602.data = {0x10, 0x12, 0x27, 0x06, 0x35, 0x34, 0x37, 0x36}; + transmit_can_frame(&TESLA_602); + stateMachineBMSReset = 3; + break; + case 3: + TESLA_602.data = {0x21, 0x31, 0x30, 0x33, 0x32, 0x3D, 0x3C, 0x3F}; + transmit_can_frame(&TESLA_602); + stateMachineBMSReset = 4; + break; + case 4: + TESLA_602.data = {0x22, 0x3E, 0x39, 0x38, 0x3B, 0x3A, 0x00, 0x00}; + transmit_can_frame(&TESLA_602); + //Should generate a CAN UDS log message indicating ECU unlocked + stateMachineBMSReset = 5; + break; + case 5: + TESLA_602.data = {0x04, 0x31, 0x01, 0x04, 0x07, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602); + stateMachineBMSReset = 0xFF; + break; + default: + //Something went wrong. Reset all and cancel + stateMachineBMSReset = 0xFF; + break; + } + } if (stateMachineBMSQuery != 0xFF) { //This implementation should be rewritten to actually reply to the UDS responses sent by the BMS //While this may work, it is not the correct way to implement this query logic diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index bafdff3f..d2116bfb 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -33,6 +33,9 @@ class TeslaBattery : public CanBattery { bool supports_reset_BMS() { return true; } void reset_BMS() { datalayer.battery.settings.user_requests_tesla_bms_reset = true; } + bool supports_reset_SOC() { return true; } + void reset_SOC() { datalayer.battery.settings.user_requests_tesla_soc_reset = true; } + bool supports_charged_energy() { return true; } BatteryHtmlRenderer& get_status_renderer() { return renderer; } @@ -486,6 +489,7 @@ class TeslaBattery : public CanBattery { uint8_t stateMachineClearIsolationFault = 0xFF; uint8_t stateMachineBMSReset = 0xFF; + uint8_t stateMachineSOCReset = 0xFF; uint8_t stateMachineBMSQuery = 0xFF; uint16_t sendContactorClosingMessagesStill = 300; uint16_t battery_cell_max_v = 3300; diff --git a/Software/src/datalayer/datalayer.h b/Software/src/datalayer/datalayer.h index ad910865..6f23ca80 100644 --- a/Software/src/datalayer/datalayer.h +++ b/Software/src/datalayer/datalayer.h @@ -152,6 +152,7 @@ struct DATALAYER_BATTERY_SETTINGS_TYPE { bool user_requests_balancing = false; bool user_requests_tesla_isolation_clear = false; bool user_requests_tesla_bms_reset = false; + bool user_requests_tesla_soc_reset = false; /* Forced balancing max time & start timestamp */ uint32_t balancing_time_ms = 3600000; //1h default, (60min*60sec*1000ms) uint32_t balancing_start_time_ms = 0; //For keeping track when balancing started diff --git a/Software/src/devboard/webserver/advanced_battery_html.cpp b/Software/src/devboard/webserver/advanced_battery_html.cpp index f52ff189..c3ea86d1 100644 --- a/Software/src/devboard/webserver/advanced_battery_html.cpp +++ b/Software/src/devboard/webserver/advanced_battery_html.cpp @@ -25,6 +25,10 @@ std::vector battery_commands = { [](Battery* b) { b->reset_BMS(); }}, + {"resetSOC", "SOC reset", "reset SOC?", [](Battery* b) { return b && b->supports_reset_SOC(); }, + [](Battery* b) { + b->reset_SOC(); + }}, {"resetCrash", "Unlock crashed BMS", "reset crash data? Note this will unlock your BMS and enable contactor closing and SOC calculation.", [](Battery* b) { return b && b->supports_reset_crash(); }, From 0961aef9aa67628d78ddac4bf7eccddf1f2c670c Mon Sep 17 00:00:00 2001 From: James Brookes Date: Sun, 24 Aug 2025 15:09:40 +0100 Subject: [PATCH 10/16] Pre-commit changes --- Software/src/battery/Battery.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/battery/Battery.h b/Software/src/battery/Battery.h index 45dd1919..8c9afa50 100644 --- a/Software/src/battery/Battery.h +++ b/Software/src/battery/Battery.h @@ -92,7 +92,7 @@ class Battery { virtual void clear_isolation() {} virtual void reset_BMS() {} - virtual void reset_SOC() {} + virtual void reset_SOC() {} virtual void reset_crash() {} virtual void reset_contactor() {} virtual void reset_NVROL() {} From dfa289ce56c4d72ed3ff8861ff6e2255b09edef9 Mon Sep 17 00:00:00 2001 From: James Brookes Date: Sun, 24 Aug 2025 15:19:25 +0100 Subject: [PATCH 11/16] Fix copy paste omissions --- Software/src/battery/TESLA-BATTERY.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 3a0f49e0..1211be41 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -2331,41 +2331,41 @@ void TeslaBattery::transmit_can(unsigned long currentMillis) { if (stateMachineSOCReset != 0xFF) { //This implementation should be rewritten to actually reply to the UDS responses sent by the BMS //While this may work, it is not the correct way to implement this - switch (stateMachineBMSReset) { + switch (stateMachineSOCReset) { case 0: TESLA_602.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}; transmit_can_frame(&TESLA_602); - stateMachineBMSReset = 1; + stateMachineSOCReset = 1; break; case 1: TESLA_602.data = {0x30, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00}; transmit_can_frame(&TESLA_602); - stateMachineBMSReset = 2; + stateMachineSOCReset = 2; break; case 2: TESLA_602.data = {0x10, 0x12, 0x27, 0x06, 0x35, 0x34, 0x37, 0x36}; transmit_can_frame(&TESLA_602); - stateMachineBMSReset = 3; + stateMachineSOCReset = 3; break; case 3: TESLA_602.data = {0x21, 0x31, 0x30, 0x33, 0x32, 0x3D, 0x3C, 0x3F}; transmit_can_frame(&TESLA_602); - stateMachineBMSReset = 4; + stateMachineSOCReset = 4; break; case 4: TESLA_602.data = {0x22, 0x3E, 0x39, 0x38, 0x3B, 0x3A, 0x00, 0x00}; transmit_can_frame(&TESLA_602); //Should generate a CAN UDS log message indicating ECU unlocked - stateMachineBMSReset = 5; + stateMachineSOCReset = 5; break; case 5: TESLA_602.data = {0x04, 0x31, 0x01, 0x04, 0x07, 0x00, 0x00, 0x00}; transmit_can_frame(&TESLA_602); - stateMachineBMSReset = 0xFF; + stateMachineSOCReset = 0xFF; break; default: //Something went wrong. Reset all and cancel - stateMachineBMSReset = 0xFF; + stateMachineSOCReset = 0xFF; break; } } From 18b24c9ee84f92fb48c8d2d53346612a8916fb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Mon, 25 Aug 2025 15:56:55 +0300 Subject: [PATCH 12/16] Add cellvoltages to CAN reading --- Software/src/battery/RELION-LV-BATTERY.cpp | 86 ++++++++++++++++++++-- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/Software/src/battery/RELION-LV-BATTERY.cpp b/Software/src/battery/RELION-LV-BATTERY.cpp index 693a5f8f..5a760f8a 100644 --- a/Software/src/battery/RELION-LV-BATTERY.cpp +++ b/Software/src/battery/RELION-LV-BATTERY.cpp @@ -38,29 +38,101 @@ void RelionBattery::update_values() { void RelionBattery::handle_incoming_can_frame(CAN_frame rx_frame) { switch (rx_frame.ID) { - case 0x02018100: //ID1 + case 0x02018100: //ID1 (Example frame 10 08 01 F0 00 00 00 00) + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; battery_total_voltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); break; - case 0x02028100: //ID2 + case 0x02028100: //ID2 (Example frame 00 00 00 63 64 10 00 00) + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; battery_total_current = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]); system_state = rx_frame.data.u8[2]; battery_soc = rx_frame.data.u8[3]; battery_soh = rx_frame.data.u8[4]; most_serious_fault = rx_frame.data.u8[5]; break; - case 0x02038100: //ID3 + case 0x02038100: //ID3 (Example frame 0C F9 01 04 0C A7 01 0F) + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; max_cell_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]); min_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); break; - case 0x02628100: //Temperatures - max_cell_temperature = rx_frame.data.u8[0] - 50; - min_cell_temperature = rx_frame.data.u8[2] - 50; - break; case 0x02648100: //Charging limitis + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; charge_current_A = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) - 800; regen_charge_current_A = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) - 800; discharge_current_A = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) - 800; break; + case 0x02048100: ///Temperatures min/max 2048100 [8] 47 01 01 47 01 01 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + max_cell_temperature = rx_frame.data.u8[0] - 50; + min_cell_temperature = rx_frame.data.u8[2] - 50; + break; + case 0x02468100: ///Raw temperatures 2468100 [8] 47 47 47 47 47 47 47 47 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02478100: ///? 2478100 [8] 32 32 32 32 32 32 32 32 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + //ID6 = 0x02108100 ~ 0x023F8100****** Cell Voltage 1~192****** + case 0x02108100: ///Cellvoltages 1 2108100 [8] 0C F9 0C F8 0C F8 0C F9 + datalayer.battery.status.cell_voltages_mV[0] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]); + datalayer.battery.status.cell_voltages_mV[1] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); + datalayer.battery.status.cell_voltages_mV[2] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + datalayer.battery.status.cell_voltages_mV[3] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]); + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02118100: ///Cellvoltages 2 2118100 [8] 0C F8 0C F8 0C F9 0C F8 + datalayer.battery.status.cell_voltages_mV[4] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]); + datalayer.battery.status.cell_voltages_mV[5] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); + datalayer.battery.status.cell_voltages_mV[6] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + datalayer.battery.status.cell_voltages_mV[7] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]); + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02128100: ///Cellvoltages 3 2128100 [8] 0C F8 0C F8 0C F9 0C F8 + datalayer.battery.status.cell_voltages_mV[8] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]); + datalayer.battery.status.cell_voltages_mV[9] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); + datalayer.battery.status.cell_voltages_mV[10] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + datalayer.battery.status.cell_voltages_mV[11] = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]); + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02138100: ///Cellvoltages 4 2138100 [8] 0C F9 0C CD 0C A7 00 00 + datalayer.battery.status.cell_voltages_mV[12] = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]); + datalayer.battery.status.cell_voltages_mV[13] = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]); + datalayer.battery.status.cell_voltages_mV[14] = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02058100: ///? 2058100 [8] 00 0C 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02068100: ///? 2068100 [8] 00 00 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02148100: ///? 2148100 [8] 00 00 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02508100: ///? 2508100 [8] 00 00 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02518100: ///? 2518100 [8] 00 00 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02528100: ///? 2528100 [8] 00 00 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02548100: ///? 2548100 [8] 00 00 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x024A8100: ///? 24A8100 [8] 00 00 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02558100: ///? 2558100 [8] 00 00 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02538100: ///? 2538100 [8] 00 00 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; + case 0x02568100: ///? 2568100 [8] 00 00 00 00 00 00 00 00 + datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE; + break; default: break; } From daabd2ba809bef94440e78f944c46e7105078e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Wed, 27 Aug 2025 21:28:02 +0300 Subject: [PATCH 13/16] Update Software.ino --- Software/Software.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/Software.ino b/Software/Software.ino index 424f3979..508a0549 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -41,7 +41,7 @@ volatile unsigned long long bmsResetTimeOffset = 0; // The current software version, shown on webserver -const char* version_number = "9.0.RC3"; +const char* version_number = "9.0.RC3experimental"; // Interval timers volatile unsigned long currentMillis = 0; From 91273c7763f9e8b8761202bd8832a9abe081774c Mon Sep 17 00:00:00 2001 From: mbuhansen <91631350+mbuhansen@users.noreply.github.com> Date: Fri, 29 Aug 2025 19:15:17 +0200 Subject: [PATCH 14/16] Fix contactor test timer start variable assignment --- Software/src/inverter/KOSTAL-RS485.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/inverter/KOSTAL-RS485.cpp b/Software/src/inverter/KOSTAL-RS485.cpp index 62ee8169..66547eeb 100644 --- a/Software/src/inverter/KOSTAL-RS485.cpp +++ b/Software/src/inverter/KOSTAL-RS485.cpp @@ -249,7 +249,7 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th datalayer.system.status.inverter_allows_contactor_closing = false; dbg_message("inverter_allows_contactor_closing -> false (Contactor test start)"); send_kostal(ACK_FRAME, 8); // ACK - contactortestTimerStart = millis(); + contactortestTimerStart = currentMillis; contactortestTimerActive = true; } else if (RS485_RXFRAME[7] == 0xFF) { // no ACK sent From 1ec6af8ea0aa3845f3a6af18fdee32c74c3adf9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C3=96ster?= Date: Fri, 29 Aug 2025 20:56:29 +0300 Subject: [PATCH 15/16] Add configurable Tesla options --- Software/src/battery/BATTERIES.cpp | 8 ++++ Software/src/battery/BATTERIES.h | 7 +++ Software/src/communication/nvm/comm_nvm.cpp | 6 +++ .../src/devboard/webserver/settings_html.cpp | 44 +++++++++++++++++++ Software/src/devboard/webserver/webserver.cpp | 16 ++++++- 5 files changed, 79 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/BATTERIES.cpp b/Software/src/battery/BATTERIES.cpp index 247e61e5..4ea2c24f 100644 --- a/Software/src/battery/BATTERIES.cpp +++ b/Software/src/battery/BATTERIES.cpp @@ -317,6 +317,14 @@ void setup_battery() { } #endif +/* User-selected Tesla settings */ +bool user_selected_tesla_digital_HVIL = false; +uint16_t user_selected_tesla_GTW_country = 0; +bool user_selected_tesla_GTW_rightHandDrive = false; +uint16_t user_selected_tesla_GTW_mapRegion = 0; +uint16_t user_selected_tesla_GTW_chassisType = 0; +uint16_t user_selected_tesla_GTW_packEnergy = 0; + /* User-selected voltages used for custom-BMS batteries (RJXZS etc.) */ #if defined(MAX_CUSTOM_PACK_VOLTAGE_DV) && defined(MIN_CUSTOM_PACK_VOLTAGE_DV) && \ defined(MAX_CUSTOM_CELL_VOLTAGE_MV) && defined(MIN_CUSTOM_CELL_VOLTAGE_MV) diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index 5c5539a6..f26dcf6e 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -61,4 +61,11 @@ extern uint16_t user_selected_min_pack_voltage_dV; extern uint16_t user_selected_max_cell_voltage_mV; extern uint16_t user_selected_min_cell_voltage_mV; +extern bool user_selected_tesla_digital_HVIL; +extern uint16_t user_selected_tesla_GTW_country; +extern bool user_selected_tesla_GTW_rightHandDrive; +extern uint16_t user_selected_tesla_GTW_mapRegion; +extern uint16_t user_selected_tesla_GTW_chassisType; +extern uint16_t user_selected_tesla_GTW_packEnergy; + #endif diff --git a/Software/src/communication/nvm/comm_nvm.cpp b/Software/src/communication/nvm/comm_nvm.cpp index 91adc099..7d513c81 100644 --- a/Software/src/communication/nvm/comm_nvm.cpp +++ b/Software/src/communication/nvm/comm_nvm.cpp @@ -103,6 +103,12 @@ void init_stored_settings() { user_selected_inverter_ah_capacity = settings.getUInt("INVAHCAPACITY", 0); user_selected_inverter_battery_type = settings.getUInt("INVBTYPE", 0); user_selected_inverter_ignore_contactors = settings.getBool("INVICNT", false); + user_selected_tesla_digital_HVIL = settings.getBool("DIGITALHVIL", false); + user_selected_tesla_GTW_country = settings.getUInt("GTWCOUNTRY", 0); + user_selected_tesla_GTW_rightHandDrive = settings.getBool("GTWRHD", false); + user_selected_tesla_GTW_mapRegion = settings.getUInt("GTWMAPREG", 0); + user_selected_tesla_GTW_chassisType = settings.getUInt("GTWCHASSIS", 0); + user_selected_tesla_GTW_packEnergy = settings.getUInt("GTWPACK", 0); auto readIf = [](const char* settingName) { auto batt1If = (comm_interface)settings.getUInt(settingName, (int)comm_interface::CanNative); diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index 7ca338ce..176a0776 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -500,6 +500,30 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti return settings.getBool("INVICNT") ? "checked" : ""; } + if (var == "DIGITALHVIL") { + return settings.getBool("DIGITALHVIL") ? "checked" : ""; + } + + if (var == "GTWCOUNTRY") { + return String(settings.getUInt("GTWCOUNTRY", 0)); + } + + if (var == "GTWRHD") { + return settings.getBool("GTWRHD") ? "checked" : ""; + } + + if (var == "GTWMAPREG") { + return String(settings.getUInt("GTWMAPREG", 0)); + } + + if (var == "GTWCHASSIS") { + return String(settings.getUInt("GTWCHASSIS", 0)); + } + + if (var == "GTWPACK") { + return String(settings.getUInt("GTWPACK", 0)); + } + return String(); } @@ -686,6 +710,11 @@ const char* getCANInterfaceName(CAN_Interface interface) { display: contents; } + form .if-tesla { display: none; } + form[data-battery="32"] .if-tesla, form[data-battery="33"] .if-tesla { + display: contents; + } + form .if-dblbtr { display: none; } form[data-dblbtr="true"] .if-dblbtr { display: contents; @@ -735,6 +764,21 @@ const char* getCANInterfaceName(CAN_Interface interface) { %BATTTYPE% +
+ + + + + + + + + + + + +
+
- - - - - - - - + + + +