Merge branch 'main' into feature/lilygo_t_2can

This commit is contained in:
Daniel Öster 2025-08-31 11:22:04 +03:00 committed by GitHub
commit e7cf83e387
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 471 additions and 85 deletions

View file

@ -41,7 +41,7 @@
volatile unsigned long long bmsResetTimeOffset = 0; volatile unsigned long long bmsResetTimeOffset = 0;
// The current software version, shown on webserver // The current software version, shown on webserver
const char* version_number = "9.0.RC3"; const char* version_number = "9.0.RC3experimental";
// Interval timers // Interval timers
volatile unsigned long currentMillis = 0; volatile unsigned long currentMillis = 0;

View file

@ -36,6 +36,7 @@
//#define DALY_BMS //#define DALY_BMS
//#define RJXZS_BMS //#define RJXZS_BMS
//#define RANGE_ROVER_PHEV_BATTERY //#define RANGE_ROVER_PHEV_BATTERY
//#define RELION_BATTERY
//#define RENAULT_KANGOO_BATTERY //#define RENAULT_KANGOO_BATTERY
//#define RENAULT_TWIZY_BATTERY //#define RENAULT_TWIZY_BATTERY
//#define RENAULT_ZOE_GEN1_BATTERY //#define RENAULT_ZOE_GEN1_BATTERY

View file

@ -110,6 +110,8 @@ const char* name_for_battery_type(BatteryType type) {
return RjxzsBms::Name; return RjxzsBms::Name;
case BatteryType::RangeRoverPhev: case BatteryType::RangeRoverPhev:
return RangeRoverPhevBattery::Name; return RangeRoverPhevBattery::Name;
case BatteryType::RelionBattery:
return RelionBattery::Name;
case BatteryType::RenaultKangoo: case BatteryType::RenaultKangoo:
return RenaultKangooBattery::Name; return RenaultKangooBattery::Name;
case BatteryType::RenaultTwizy: case BatteryType::RenaultTwizy:
@ -213,6 +215,8 @@ Battery* create_battery(BatteryType type) {
return new RjxzsBms(); return new RjxzsBms();
case BatteryType::RangeRoverPhev: case BatteryType::RangeRoverPhev:
return new RangeRoverPhevBattery(); return new RangeRoverPhevBattery();
case BatteryType::RelionBattery:
return new RelionBattery();
case BatteryType::RenaultKangoo: case BatteryType::RenaultKangoo:
return new RenaultKangooBattery(); return new RenaultKangooBattery();
case BatteryType::RenaultTwizy: case BatteryType::RenaultTwizy:
@ -317,6 +321,14 @@ void setup_battery() {
} }
#endif #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.) */ /* User-selected voltages used for custom-BMS batteries (RJXZS etc.) */
#if defined(MAX_CUSTOM_PACK_VOLTAGE_DV) && defined(MIN_CUSTOM_PACK_VOLTAGE_DV) && \ #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) defined(MAX_CUSTOM_CELL_VOLTAGE_MV) && defined(MIN_CUSTOM_CELL_VOLTAGE_MV)

View file

@ -40,6 +40,7 @@ void setup_can_shunt();
#include "ORION-BMS.h" #include "ORION-BMS.h"
#include "PYLON-BATTERY.h" #include "PYLON-BATTERY.h"
#include "RANGE-ROVER-PHEV-BATTERY.h" #include "RANGE-ROVER-PHEV-BATTERY.h"
#include "RELION-LV-BATTERY.h"
#include "RENAULT-KANGOO-BATTERY.h" #include "RENAULT-KANGOO-BATTERY.h"
#include "RENAULT-TWIZY.h" #include "RENAULT-TWIZY.h"
#include "RENAULT-ZOE-GEN1-BATTERY.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_max_cell_voltage_mV;
extern uint16_t user_selected_min_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 #endif

View file

@ -46,6 +46,7 @@ enum class BatteryType {
SamsungSdiLv = 38, SamsungSdiLv = 38,
HyundaiIoniq28 = 39, HyundaiIoniq28 = 39,
Kia64FD = 40, Kia64FD = 40,
RelionBattery = 41,
Highest Highest
}; };
@ -74,6 +75,7 @@ class Battery {
virtual bool supports_clear_isolation() { return false; } virtual bool supports_clear_isolation() { return false; }
virtual bool supports_reset_BMS() { 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_crash() { return false; }
virtual bool supports_reset_NVROL() { return false; } virtual bool supports_reset_NVROL() { return false; }
virtual bool supports_reset_DTC() { return false; } virtual bool supports_reset_DTC() { return false; }
@ -92,6 +94,7 @@ class Battery {
virtual void clear_isolation() {} virtual void clear_isolation() {}
virtual void reset_BMS() {} virtual void reset_BMS() {}
virtual void reset_SOC() {}
virtual void reset_crash() {} virtual void reset_crash() {}
virtual void reset_contactor() {} virtual void reset_contactor() {}
virtual void reset_NVROL() {} virtual void reset_NVROL() {}

View file

@ -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<uint32_t>(
(static_cast<double>(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;
}

View file

@ -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

View file

@ -676,39 +676,39 @@ void TeslaBattery::
clear_event(EVENT_BATTERY_FUSE); clear_event(EVENT_BATTERY_FUSE);
} }
#ifdef TESLA_MODEL_3Y_BATTERY 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. // Autodetect algoritm for chemistry on 3/Y packs.
// NCM/A batteries have 96s, LFP has 102-108s // 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! // Drawback with this check is that it takes 3-5minutes before all cells have been counted!
if (datalayer.battery.info.number_of_cells > 101) { if (datalayer.battery.info.number_of_cells > 101) {
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
} }
//Once cell chemistry is determined, set maximum and minimum total pack voltage safety limits //Once cell chemistry is determined, set maximum and minimum total pack voltage safety limits
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_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.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_LFP;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_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.min_cell_voltage_mV = MIN_CELL_VOLTAGE_LFP;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_LFP; datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_LFP;
} else { // NCM/A chemistry } else { // NCM/A chemistry
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_NCMA; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_3Y_NCMA;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM; 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.min_cell_voltage_mV = MIN_CELL_VOLTAGE_NCA_NCM;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_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 // During forced balancing request via webserver, we allow the battery to exceed normal safety parameters
if (datalayer.battery.settings.user_requests_balancing) { if (datalayer.battery.settings.user_requests_balancing) {
datalayer.battery.status.real_soc = 9900; //Force battery to show up as 99% when 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_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_mV = datalayer.battery.settings.balancing_max_cell_voltage_mV;
datalayer.battery.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV =
datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV; datalayer.battery.settings.balancing_max_deviation_cell_voltage_mV;
datalayer.battery.status.max_charge_power_W = datalayer.battery.settings.balancing_float_power_W; 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 // Check if user requests some action
if (datalayer.battery.settings.user_requests_tesla_isolation_clear) { if (datalayer.battery.settings.user_requests_tesla_isolation_clear) {
@ -727,10 +727,26 @@ void TeslaBattery::
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("ERROR: BMS reset failed due to contactors not being open, or BMS ECU not allowing it"); logging.println("ERROR: BMS reset failed due to contactors not being open, or BMS ECU not allowing it");
#endif //DEBUG_LOG #endif //DEBUG_LOG
stateMachineBMSReset = 0; stateMachineBMSReset = 0xFF;
datalayer.battery.settings.user_requests_tesla_bms_reset = false; 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? //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 //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) { 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 ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) {
if (currentMillis - lastSend1CF >= 10) { if (currentMillis - lastSend1CF >= 10) {
transmit_can_frame(&can_msg_1CF[index_1CF]); transmit_can_frame(&can_msg_1CF[index_1CF]);
@ -2312,6 +2328,47 @@ void TeslaBattery::transmit_can(unsigned long currentMillis) {
break; 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) { if (stateMachineBMSQuery != 0xFF) {
//This implementation should be rewritten to actually reply to the UDS responses sent by the BMS //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 //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 //0x7FF GTW CAN frame values
//Mux1 //Mux1
write_signal_value(&TESLA_7FF_Mux1, 16, 16, GTW_country, false); write_signal_value(&TESLA_7FF_Mux1, 16, 16, user_selected_tesla_GTW_country, false);
write_signal_value(&TESLA_7FF_Mux1, 11, 1, GTW_rightHandDrive, false); write_signal_value(&TESLA_7FF_Mux1, 11, 1, user_selected_tesla_GTW_country, false);
//Mux3 //Mux3
write_signal_value(&TESLA_7FF_Mux3, 8, 4, GTW_mapRegion, false); write_signal_value(&TESLA_7FF_Mux3, 8, 4, user_selected_tesla_GTW_mapRegion, false);
write_signal_value(&TESLA_7FF_Mux3, 18, 3, GTW_chassisType, false); write_signal_value(&TESLA_7FF_Mux3, 18, 3, user_selected_tesla_GTW_chassisType, false);
write_signal_value(&TESLA_7FF_Mux3, 32, 5, GTW_packEnergy, false); write_signal_value(&TESLA_7FF_Mux3, 32, 5, user_selected_tesla_GTW_packEnergy, false);
strncpy(datalayer.system.info.battery_protocol, Name, 63); strncpy(datalayer.system.info.battery_protocol, Name, 63);
datalayer.system.info.battery_protocol[63] = '\0'; datalayer.system.info.battery_protocol[63] = '\0';

View file

@ -11,12 +11,14 @@
#define SELECTED_BATTERY_CLASS TeslaModelSXBattery #define SELECTED_BATTERY_CLASS TeslaModelSXBattery
#endif #endif
/*NOTE! IMPORTANT INFORMATION! // 0x7FF gateway config, "Gen3" vehicles only, not applicable to Gen2 "classic" Model S and Model X
Be sure to scroll down in this file and configure all "GTW_" parameters to suit your battery. // These are user configurable from the Webserver UI
Failure to configure these will result in VCFRONT and GTW MIA errors extern bool user_selected_tesla_digital_HVIL;
*/ extern uint16_t user_selected_tesla_GTW_country;
extern bool user_selected_tesla_GTW_rightHandDrive;
//#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. 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 { class TeslaBattery : public CanBattery {
public: public:
@ -33,6 +35,9 @@ class TeslaBattery : public CanBattery {
bool supports_reset_BMS() { return true; } bool supports_reset_BMS() { return true; }
void reset_BMS() { datalayer.battery.settings.user_requests_tesla_bms_reset = 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; } bool supports_charged_energy() { return true; }
BatteryHtmlRenderer& get_status_renderer() { return renderer; } 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 // Set this to true to try to close contactors/full startup even with no inverter defined/connected
bool batteryTestOverride = false; 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! */ /* 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 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 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 stateMachineClearIsolationFault = 0xFF;
uint8_t stateMachineBMSReset = 0xFF; uint8_t stateMachineBMSReset = 0xFF;
uint8_t stateMachineSOCReset = 0xFF;
uint8_t stateMachineBMSQuery = 0xFF; uint8_t stateMachineBMSQuery = 0xFF;
uint16_t sendContactorClosingMessagesStill = 300; uint16_t sendContactorClosingMessagesStill = 300;
uint16_t battery_cell_max_v = 3300; uint16_t battery_cell_max_v = 3300;
@ -922,19 +913,14 @@ class TeslaBattery : public CanBattery {
class TeslaModel3YBattery : public TeslaBattery { class TeslaModel3YBattery : public TeslaBattery {
public: public:
TeslaModel3YBattery(battery_chemistry_enum chemistry) { TeslaModel3YBattery(battery_chemistry_enum chemistry) { datalayer.battery.info.chemistry = chemistry; }
datalayer.battery.info.chemistry = chemistry;
#ifdef EXP_TESLA_BMS_DIGITAL_HVIL
operate_contactors = true;
#endif
}
static constexpr const char* Name = "Tesla Model 3/Y"; static constexpr const char* Name = "Tesla Model 3/Y";
virtual void setup(void); virtual void setup(void);
}; };
class TeslaModelSXBattery : public TeslaBattery { class TeslaModelSXBattery : public TeslaBattery {
public: public:
TeslaModelSXBattery() { operate_contactors = true; } TeslaModelSXBattery() {}
static constexpr const char* Name = "Tesla Model S/X"; static constexpr const char* Name = "Tesla Model S/X";
virtual void setup(void); virtual void setup(void);
}; };

View file

@ -106,6 +106,12 @@ void init_stored_settings() {
user_selected_inverter_battery_type = settings.getUInt("INVBTYPE", 0); user_selected_inverter_battery_type = settings.getUInt("INVBTYPE", 0);
user_selected_inverter_ignore_contactors = settings.getBool("INVICNT", false); user_selected_inverter_ignore_contactors = settings.getBool("INVICNT", false);
user_selected_can_addon_crystal_frequency_mhz = settings.getUInt("CANFREQ", 8); 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 readIf = [](const char* settingName) {
auto batt1If = (comm_interface)settings.getUInt(settingName, (int)comm_interface::CanNative); auto batt1If = (comm_interface)settings.getUInt(settingName, (int)comm_interface::CanNative);

View file

@ -64,6 +64,14 @@ void handle_precharge_control(unsigned long currentMillis) {
auto hia4v1_pin = esp32hal->HIA4V1_PIN(); auto hia4v1_pin = esp32hal->HIA4V1_PIN();
auto inverter_disconnect_contactor_pin = esp32hal->INVERTER_DISCONNECT_CONTACTOR_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 target_voltage = datalayer.battery.status.voltage_dV;
int32_t external_voltage = datalayer_extended.meb.BMS_voltage_intermediate_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); pinMode(hia4v1_pin, OUTPUT);
digitalWrite(hia4v1_pin, LOW); digitalWrite(hia4v1_pin, LOW);
digitalWrite(inverter_disconnect_contactor_pin, ON); 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 #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 #endif
set_event(EVENT_AUTOMATIC_PRECHARGE_FAILURE, 0); set_event(EVENT_AUTOMATIC_PRECHARGE_FAILURE, 0);
// Force stop any further precharge attempts
datalayer.system.settings.start_precharging = false;
// Add event // Add event
} else if (datalayer.system.status.battery_allows_contactor_closing) { } else if (datalayer.system.status.battery_allows_contactor_closing) {

View file

@ -152,6 +152,7 @@ struct DATALAYER_BATTERY_SETTINGS_TYPE {
bool user_requests_balancing = false; bool user_requests_balancing = false;
bool user_requests_tesla_isolation_clear = false; bool user_requests_tesla_isolation_clear = false;
bool user_requests_tesla_bms_reset = false; bool user_requests_tesla_bms_reset = false;
bool user_requests_tesla_soc_reset = false;
/* Forced balancing max time & start timestamp */ /* Forced balancing max time & start timestamp */
uint32_t balancing_time_ms = 3600000; //1h default, (60min*60sec*1000ms) uint32_t balancing_time_ms = 3600000; //1h default, (60min*60sec*1000ms)
uint32_t balancing_start_time_ms = 0; //For keeping track when balancing started uint32_t balancing_start_time_ms = 0; //For keeping track when balancing started

View file

@ -263,7 +263,7 @@ String get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_PRECHARGE_FAILURE: case EVENT_PRECHARGE_FAILURE:
return "Battery failed to precharge. Check that capacitor is seated on high voltage output."; return "Battery failed to precharge. Check that capacitor is seated on high voltage output.";
case EVENT_AUTOMATIC_PRECHARGE_FAILURE: 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: case EVENT_INTERNAL_OPEN_FAULT:
return "High voltage cable removed while battery running. Opening contactors!"; return "High voltage cable removed while battery running. Opening contactors!";
case EVENT_INVERTER_OPEN_CONTACTOR: case EVENT_INVERTER_OPEN_CONTACTOR:

View file

@ -27,7 +27,8 @@ enum PrechargeState {
AUTO_PRECHARGE_START, AUTO_PRECHARGE_START,
AUTO_PRECHARGE_PRECHARGING, AUTO_PRECHARGE_PRECHARGING,
AUTO_PRECHARGE_OFF, AUTO_PRECHARGE_OFF,
AUTO_PRECHARGE_COMPLETED AUTO_PRECHARGE_COMPLETED,
AUTO_PRECHARGE_FAILURE
}; };
#define DISCHARGING 1 #define DISCHARGING 1

View file

@ -25,6 +25,10 @@ std::vector<BatteryCommand> battery_commands = {
[](Battery* b) { [](Battery* b) {
b->reset_BMS(); 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", {"resetCrash", "Unlock crashed BMS",
"reset crash data? Note this will unlock your BMS and enable contactor closing and SOC calculation.", "reset crash data? Note this will unlock your BMS and enable contactor closing and SOC calculation.",
[](Battery* b) { return b && b->supports_reset_crash(); }, [](Battery* b) { return b && b->supports_reset_crash(); },

View file

@ -78,6 +78,33 @@ String options_for_enum(TEnum selected, Func name_for_type) {
return options; return options;
} }
template <typename TMap>
String options_from_map(int selected, const TMap& value_name_map) {
String options;
for (const auto& [value, name] : value_name_map) {
options += "<option value=\"" + String(value) + "\"";
if (selected == value) {
options += " selected";
}
options += ">";
options += name;
options += "</option>";
}
return options;
}
static const std::map<int, String> tesla_countries = {
{21843, "US (USA)"}, {17217, "CA (Canada)"}, {18242, "GB (UK & N Ireland)"},
{17483, "DK (Denmark)"}, {17477, "DE (Germany)"}, {16725, "AU (Australia)"}};
static const std::map<int, String> tesla_mapregion = {
{8, "ME (Middle East)"}, {2, "NONE"}, {3, "CN (China)"}, {6, "TW (Taiwan)"}, {5, "JP (Japan)"},
{0, "US (USA)"}, {7, "KR (Korea)"}, {4, "AU (Australia)"}, {1, "EU (Europe)"}};
static const std::map<int, String> tesla_chassis = {{0, "Model S"}, {1, "Model X"}, {2, "Model 3"}, {3, "Model Y"}};
static const std::map<int, String> tesla_pack = {{0, "50 kWh"}, {2, "62 kWh"}, {1, "74 kWh"}, {3, "100 kWh"}};
const char* name_for_button_type(STOP_BUTTON_BEHAVIOR behavior) { const char* name_for_button_type(STOP_BUTTON_BEHAVIOR behavior) {
switch (behavior) { switch (behavior) {
case STOP_BUTTON_BEHAVIOR::LATCHING_SWITCH: case STOP_BUTTON_BEHAVIOR::LATCHING_SWITCH:
@ -504,6 +531,30 @@ String settings_processor(const String& var, BatteryEmulatorSettingsStore& setti
return String(settings.getUInt("CANFREQ", 8)); return String(settings.getUInt("CANFREQ", 8));
} }
if (var == "DIGITALHVIL") {
return settings.getBool("DIGITALHVIL") ? "checked" : "";
}
if (var == "GTWCOUNTRY") {
return options_from_map(settings.getUInt("GTWCOUNTRY", 0), tesla_countries);
}
if (var == "GTWRHD") {
return settings.getBool("GTWRHD") ? "checked" : "";
}
if (var == "GTWMAPREG") {
return options_from_map(settings.getUInt("GTWMAPREG", 0), tesla_mapregion);
}
if (var == "GTWCHASSIS") {
return options_from_map(settings.getUInt("GTWCHASSIS", 0), tesla_chassis);
}
if (var == "GTWPACK") {
return options_from_map(settings.getUInt("GTWPACK", 0), tesla_pack);
}
return String(); return String();
} }
@ -690,6 +741,11 @@ const char* getCANInterfaceName(CAN_Interface interface) {
display: contents; 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 .if-dblbtr { display: none; }
form[data-dblbtr="true"] .if-dblbtr { form[data-dblbtr="true"] .if-dblbtr {
display: contents; display: contents;
@ -739,6 +795,25 @@ const char* getCANInterfaceName(CAN_Interface interface) {
%BATTTYPE% %BATTTYPE%
</select> </select>
<div class="if-tesla">
<label>Digital HVIL (2024+): </label>
<input type='checkbox' name='DIGITALHVIL' value='on' style='margin-left: 0;' %DIGITALHVIL% />
<label>Right hand drive: </label>
<input type='checkbox' name='GTWRHD' value='on' style='margin-left: 0;' %GTWRHD% />
<label for='GTWCOUNTRY'>Country code: </label><select name='GTWCOUNTRY' id='GTWCOUNTRY'>
%GTWCOUNTRY%
</select>
<label for='GTWMAPREG'>Map region: </label><select name='GTWMAPREG' id='GTWMAPREG'>
%GTWMAPREG%
</select>
<label for='GTWCHASSIS'>Chassis type: </label><select name='GTWCHASSIS' id='GTWCHASSIS'>
%GTWCHASSIS%
</select>
<label for='GTWPACK'>Pack type: </label><select name='GTWPACK' id='GTWPACK'>
%GTWPACK%
</select>
</div>
<div class="if-battery"> <div class="if-battery">
<label for='BATTCOMM'>Battery comm I/F: </label><select name='BATTCOMM' id='BATTCOMM'> <label for='BATTCOMM'>Battery comm I/F: </label><select name='BATTCOMM' id='BATTCOMM'>
%BATTCOMM% %BATTCOMM%

View file

@ -413,8 +413,8 @@ void init_webserver() {
}; };
const char* boolSettingNames[] = { const char* boolSettingNames[] = {
"DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "REMBMSRESET", "DBLBTR", "CNTCTRL", "CNTCTRLDBL", "PWMCNTCTRL", "PERBMSRESET", "REMBMSRESET", "CANFDASCAN",
"CANFDASCAN", "WIFIAPENABLED", "MQTTENABLED", "HADISC", "MQTTTOPICS", "INVICNT", "WIFIAPENABLED", "MQTTENABLED", "HADISC", "MQTTTOPICS", "INVICNT", "GTWRHD", "DIGITALHVIL",
}; };
// Handles the form POST from UI to save settings of the common image // Handles the form POST from UI to save settings of the common image
@ -518,6 +518,18 @@ void init_webserver() {
} else if (p->name() == "CANFREQ") { } else if (p->name() == "CANFREQ") {
auto type = atoi(p->value().c_str()); auto type = atoi(p->value().c_str());
settings.saveUInt("CANFREQ", type); settings.saveUInt("CANFREQ", type);
} else if (p->name() == "GTWCOUNTRY") {
auto type = atoi(p->value().c_str());
settings.saveUInt("GTWCOUNTRY", type);
} else if (p->name() == "GTWMAPREG") {
auto type = atoi(p->value().c_str());
settings.saveUInt("GTWMAPREG", type);
} else if (p->name() == "GTWCHASSIS") {
auto type = atoi(p->value().c_str());
settings.saveUInt("GTWCHASSIS", type);
} else if (p->name() == "GTWPACK") {
auto type = atoi(p->value().c_str());
settings.saveUInt("GTWPACK", type);
} }
for (auto& boolSetting : boolSettings) { for (auto& boolSetting : boolSettings) {

View file

@ -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, 18); // Last current
float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 22); // Should be Avg current(1s) float2frame(CYCLIC_DATA, (float)datalayer.battery.status.current_dA / 10, 22); // Should be Avg current(1s)
// Close contactors after 7 battery info frames requested // Close contactors after 20 battery info frames requested
if (f2_startup_count > 7) { if (f2_startup_count > 20) {
datalayer.system.status.inverter_allows_contactor_closing = true; 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 (datalayer.system.status.inverter_allows_contactor_closing) {
if (f2_startup_count < 9) {
CYCLIC_DATA[56] = 0x00;
} else {
CYCLIC_DATA[56] = 0x01; 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; CYCLIC_DATA[59] = 0x00;
} else {
CYCLIC_DATA[56] = 0x00;
CYCLIC_DATA[59] = 0x02;
} }
#endif #endif
@ -214,6 +208,12 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th
{ {
currentMillis = millis(); 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) { if (datalayer.system.status.battery_allows_contactor_closing & !contactorMillis) {
contactorMillis = currentMillis; contactorMillis = currentMillis;
} }
@ -240,9 +240,17 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th
if (RS485_RXFRAME[6] == 0x5E) { if (RS485_RXFRAME[6] == 0x5E) {
// Set State function // Set State function
if (RS485_RXFRAME[7] == 0x00) { 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 send_kostal(ACK_FRAME, 8); // ACK
} else if (RS485_RXFRAME[7] == 0x04) { } 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 send_kostal(ACK_FRAME, 8); // ACK
contactortestTimerStart = currentMillis;
contactortestTimerActive = true;
} else if (RS485_RXFRAME[7] == 0xFF) { } else if (RS485_RXFRAME[7] == 0xFF) {
// no ACK sent // no ACK sent
} else { } else {
@ -279,6 +287,8 @@ void KostalInverterProtocol::receive() // Runs as fast as possible to handle th
tmpframe[38] = calculate_kostal_crc(tmpframe, 38); tmpframe[38] = calculate_kostal_crc(tmpframe, 38);
null_stuffer(tmpframe, 40); null_stuffer(tmpframe, 40);
send_kostal(tmpframe, 40); send_kostal(tmpframe, 40);
datalayer.system.status.inverter_allows_contactor_closing = false;
dbg_message("inverter_allows_contactor_closing -> false (battery info sent)");
info_sent = true; info_sent = true;
if (!startupMillis) { if (!startupMillis) {
startupMillis = currentMillis; startupMillis = currentMillis;

View file

@ -43,6 +43,8 @@ class KostalInverterProtocol : public Rs485InverterProtocol {
unsigned long currentMillis; unsigned long currentMillis;
unsigned long startupMillis = 0; unsigned long startupMillis = 0;
unsigned long contactorMillis = 0; unsigned long contactorMillis = 0;
unsigned long contactortestTimerStart = 0;
bool contactortestTimerActive = false;
uint16_t rx_index = 0; uint16_t rx_index = 0;
bool RX_allow = false; bool RX_allow = false;