diff --git a/Software/Software.ino b/Software/Software.ino index bc395931..fc8ed984 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; diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index ff6ccfe0..42b7cc3e 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -36,6 +36,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 247e61e5..10134bf2 100644 --- a/Software/src/battery/BATTERIES.cpp +++ b/Software/src/battery/BATTERIES.cpp @@ -110,6 +110,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: @@ -213,6 +215,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: @@ -317,6 +321,14 @@ void setup_battery() { } #endif +/* User-selected Tesla settings */ +bool user_selected_tesla_digital_HVIL = false; +uint16_t user_selected_tesla_GTW_country = 17477; +bool user_selected_tesla_GTW_rightHandDrive = true; +uint16_t user_selected_tesla_GTW_mapRegion = 2; +uint16_t user_selected_tesla_GTW_chassisType = 2; +uint16_t user_selected_tesla_GTW_packEnergy = 1; + /* 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..78e55398 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -40,6 +40,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" @@ -61,4 +62,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/battery/Battery.h b/Software/src/battery/Battery.h index d22fcb93..0b99f588 100644 --- a/Software/src/battery/Battery.h +++ b/Software/src/battery/Battery.h @@ -46,6 +46,7 @@ enum class BatteryType { SamsungSdiLv = 38, HyundaiIoniq28 = 39, Kia64FD = 40, + RelionBattery = 41, Highest }; @@ -74,6 +75,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; } @@ -92,6 +94,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/RELION-LV-BATTERY.cpp b/Software/src/battery/RELION-LV-BATTERY.cpp new file mode 100644 index 00000000..5a760f8a --- /dev/null +++ b/Software/src/battery/RELION-LV-BATTERY.cpp @@ -0,0 +1,155 @@ +#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 (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 (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 (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 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; + } +} + +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 diff --git a/Software/src/battery/TESLA-BATTERY.cpp b/Software/src/battery/TESLA-BATTERY.cpp index 32599f93..96c38315 100644 --- a/Software/src/battery/TESLA-BATTERY.cpp +++ b/Software/src/battery/TESLA-BATTERY.cpp @@ -676,39 +676,39 @@ void TeslaBattery:: clear_event(EVENT_BATTERY_FUSE); } -#ifdef TESLA_MODEL_3Y_BATTERY - // Autodetect algoritm for chemistry on 3/Y packs. - // NCM/A batteries have 96s, LFP has 102-108s - // Drawback with this check is that it takes 3-5minutes before all cells have been counted! - if (datalayer.battery.info.number_of_cells > 101) { - datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; - } + if (user_selected_tesla_GTW_chassisType > 1) { //{{0, "Model S"}, {1, "Model X"}, {2, "Model 3"}, {3, "Model Y"}}; + // Autodetect algoritm for chemistry on 3/Y packs. + // NCM/A batteries have 96s, LFP has 102-108s + // Drawback with this check is that it takes 3-5minutes before all cells have been counted! + if (datalayer.battery.info.number_of_cells > 101) { + datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; + } - //Once cell chemistry is determined, set maximum and minimum total pack voltage safety limits - if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { - datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP; - datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_LFP; - datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_LFP; - datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_LFP; - datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_LFP; - } else { // NCM/A chemistry - datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_NCMA; - datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_NCMA; - datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM; - datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_NCA_NCM; - datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_NCA_NCM; - } + //Once cell chemistry is determined, set maximum and minimum total pack voltage safety limits + if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_LFP; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_LFP; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_LFP; + datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_LFP; + } else { // NCM/A chemistry + datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_NCMA; + datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_NCMA; + datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM; + datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_NCA_NCM; + datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_NCA_NCM; + } - // During forced balancing request via webserver, we allow the battery to exceed normal safety parameters - if (datalayer.battery.settings.user_requests_balancing) { - datalayer.battery.status.real_soc = 9900; //Force battery to show up as 99% when balancing - datalayer.battery.info.max_design_voltage_dV = datalayer.battery.settings.balancing_max_pack_voltage_dV; - datalayer.battery.info.max_cell_voltage_mV = datalayer.battery.settings.balancing_max_cell_voltage_mV; - datalayer.battery.info.max_cell_voltage_deviation_mV = - datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV; - datalayer.battery.status.max_charge_power_W = datalayer.battery.settings.balancing_float_power_W; + // During forced balancing request via webserver, we allow the battery to exceed normal safety parameters + if (datalayer.battery.settings.user_requests_balancing) { + datalayer.battery.status.real_soc = 9900; //Force battery to show up as 99% when balancing + datalayer.battery.info.max_design_voltage_dV = datalayer.battery.settings.balancing_max_pack_voltage_dV; + datalayer.battery.info.max_cell_voltage_mV = datalayer.battery.settings.balancing_max_cell_voltage_mV; + datalayer.battery.info.max_cell_voltage_deviation_mV = + datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV; + datalayer.battery.status.max_charge_power_W = datalayer.battery.settings.balancing_float_power_W; + } } -#endif // TESLA_MODEL_3Y_BATTERY // Check if user requests some action if (datalayer.battery.settings.user_requests_tesla_isolation_clear) { @@ -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 @@ -1999,7 +2015,7 @@ int index_118 = 0; void TeslaBattery::transmit_can(unsigned long currentMillis) { - if (operate_contactors) { //Special S/X mode + if (user_selected_tesla_digital_HVIL) { //Special S/X? mode for 2024+ batteries if ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) { if (currentMillis - lastSend1CF >= 10) { transmit_can_frame(&can_msg_1CF[index_1CF]); @@ -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 (stateMachineSOCReset) { + case 0: + TESLA_602.data = {0x02, 0x27, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602); + stateMachineSOCReset = 1; + break; + case 1: + TESLA_602.data = {0x30, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602); + stateMachineSOCReset = 2; + break; + case 2: + TESLA_602.data = {0x10, 0x12, 0x27, 0x06, 0x35, 0x34, 0x37, 0x36}; + transmit_can_frame(&TESLA_602); + stateMachineSOCReset = 3; + break; + case 3: + TESLA_602.data = {0x21, 0x31, 0x30, 0x33, 0x32, 0x3D, 0x3C, 0x3F}; + transmit_can_frame(&TESLA_602); + 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 + stateMachineSOCReset = 5; + break; + case 5: + TESLA_602.data = {0x04, 0x31, 0x01, 0x04, 0x07, 0x00, 0x00, 0x00}; + transmit_can_frame(&TESLA_602); + stateMachineSOCReset = 0xFF; + break; + default: + //Something went wrong. Reset all and cancel + stateMachineSOCReset = 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 @@ -2571,12 +2628,12 @@ void TeslaModel3YBattery::setup(void) { // Performs one time setup at startup //0x7FF GTW CAN frame values //Mux1 - write_signal_value(&TESLA_7FF_Mux1, 16, 16, GTW_country, false); - write_signal_value(&TESLA_7FF_Mux1, 11, 1, GTW_rightHandDrive, false); + write_signal_value(&TESLA_7FF_Mux1, 16, 16, user_selected_tesla_GTW_country, false); + write_signal_value(&TESLA_7FF_Mux1, 11, 1, user_selected_tesla_GTW_country, false); //Mux3 - write_signal_value(&TESLA_7FF_Mux3, 8, 4, GTW_mapRegion, false); - write_signal_value(&TESLA_7FF_Mux3, 18, 3, GTW_chassisType, false); - write_signal_value(&TESLA_7FF_Mux3, 32, 5, GTW_packEnergy, false); + write_signal_value(&TESLA_7FF_Mux3, 8, 4, user_selected_tesla_GTW_mapRegion, false); + write_signal_value(&TESLA_7FF_Mux3, 18, 3, user_selected_tesla_GTW_chassisType, false); + write_signal_value(&TESLA_7FF_Mux3, 32, 5, user_selected_tesla_GTW_packEnergy, false); strncpy(datalayer.system.info.battery_protocol, Name, 63); datalayer.system.info.battery_protocol[63] = '\0'; diff --git a/Software/src/battery/TESLA-BATTERY.h b/Software/src/battery/TESLA-BATTERY.h index bafdff3f..fca5a83b 100644 --- a/Software/src/battery/TESLA-BATTERY.h +++ b/Software/src/battery/TESLA-BATTERY.h @@ -11,12 +11,14 @@ #define SELECTED_BATTERY_CLASS TeslaModelSXBattery #endif -/*NOTE! IMPORTANT INFORMATION! -Be sure to scroll down in this file and configure all "GTW_" parameters to suit your battery. -Failure to configure these will result in VCFRONT and GTW MIA errors -*/ - -//#define EXP_TESLA_BMS_DIGITAL_HVIL // Experimental parameter. Forces the transmission of additional CAN frames for experimental purposes, to test potential HVIL issues in 3/Y packs with newer firmware. +// 0x7FF gateway config, "Gen3" vehicles only, not applicable to Gen2 "classic" Model S and Model X +// These are user configurable from the Webserver UI +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; class TeslaBattery : public CanBattery { public: @@ -33,6 +35,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; } @@ -50,21 +55,6 @@ class TeslaBattery : public CanBattery { // Set this to true to try to close contactors/full startup even with no inverter defined/connected bool batteryTestOverride = false; - // 0x7FF gateway config, "Gen3" vehicles only, not applicable to Gen2 "classic" Model S and Model X - // - // ** MANUALLY SET FOR NOW **, TODO: change based on USER_SETTINGS.h or preset - // - static const uint16_t GTW_country = - 18242; // "US" (USA): 21843, "CA" (Canada): 17217, "GB" (UK & N Ireland): 18242, "DK" (Denmark): 17483, "DE" (Germany): 17477, "AU" (Australia): 16725 [HVP shows errors if EU/US region mismatch for example] - // GTW_country is ISO 3166-1 Alpha-2 code, each letter converted to binary (8-bit chunks), those 8-bit chunks concatenated and then converted to decimal - static const uint8_t GTW_rightHandDrive = - 1; // Left: 0, Right: 1 (not sure this matters but there for consistency in emulating the car - make sure correct for GTW_country, e.g. 0 for USA) - static const uint8_t GTW_mapRegion = - 1; // "ME": 8, "NONE": 2, "CN": 3, "TW": 6, "JP": 5, "US": 0, "KR": 7, "AU": 4, "EU": 1 (not sure this matters but there for consistency) - static const uint8_t GTW_chassisType = - 2; // "MODEL_3_CHASSIS": 2, "MODEL_Y_CHASSIS": 3 ("MODEL_S_CHASSIS": 0, "MODEL_X_CHASSIS": 1) - static const uint8_t GTW_packEnergy = 1; // "PACK_50_KWH": 0, "PACK_74_KWH": 1, "PACK_62_KWH": 2, "PACK_100_KWH": 3 - /* Do not change anything below this line! */ static const int RAMPDOWN_SOC = 900; // 90.0 SOC% to start ramping down from max charge power towards 0 at 100.00% static const int RAMPDOWNPOWERALLOWED = 10000; // What power we ramp down from towards top balancing @@ -486,6 +476,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; @@ -922,19 +913,14 @@ class TeslaBattery : public CanBattery { class TeslaModel3YBattery : public TeslaBattery { public: - TeslaModel3YBattery(battery_chemistry_enum chemistry) { - datalayer.battery.info.chemistry = chemistry; -#ifdef EXP_TESLA_BMS_DIGITAL_HVIL - operate_contactors = true; -#endif - } + TeslaModel3YBattery(battery_chemistry_enum chemistry) { datalayer.battery.info.chemistry = chemistry; } static constexpr const char* Name = "Tesla Model 3/Y"; virtual void setup(void); }; class TeslaModelSXBattery : public TeslaBattery { public: - TeslaModelSXBattery() { operate_contactors = true; } + TeslaModelSXBattery() {} static constexpr const char* Name = "Tesla Model S/X"; virtual void setup(void); }; diff --git a/Software/src/communication/nvm/comm_nvm.cpp b/Software/src/communication/nvm/comm_nvm.cpp index 9a2233f3..36b69818 100644 --- a/Software/src/communication/nvm/comm_nvm.cpp +++ b/Software/src/communication/nvm/comm_nvm.cpp @@ -106,6 +106,12 @@ void init_stored_settings() { user_selected_inverter_battery_type = settings.getUInt("INVBTYPE", 0); user_selected_inverter_ignore_contactors = settings.getBool("INVICNT", false); user_selected_can_addon_crystal_frequency_mhz = settings.getUInt("CANFREQ", 8); + 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/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/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/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 2ce9c1bf..045fea57 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -263,7 +263,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: diff --git a/Software/src/devboard/utils/types.h b/Software/src/devboard/utils/types.h index 45ec6034..846d6c43 100644 --- a/Software/src/devboard/utils/types.h +++ b/Software/src/devboard/utils/types.h @@ -27,7 +27,8 @@ enum PrechargeState { AUTO_PRECHARGE_START, AUTO_PRECHARGE_PRECHARGING, AUTO_PRECHARGE_OFF, - AUTO_PRECHARGE_COMPLETED + AUTO_PRECHARGE_COMPLETED, + AUTO_PRECHARGE_FAILURE }; #define DISCHARGING 1 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(); }, diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index 8b1bf9ee..759046e4 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -78,6 +78,33 @@ String options_for_enum(TEnum selected, Func name_for_type) { return options; } +template +String options_from_map(int selected, const TMap& value_name_map) { + String options; + for (const auto& [value, name] : value_name_map) { + options += "