Merge branch 'main' into kostal-class

This commit is contained in:
Jaakko Haakana 2025-05-12 07:57:20 +03:00
commit cdc4558c7f
10 changed files with 1060 additions and 928 deletions

View file

@ -25,9 +25,15 @@ void setup_battery() {
#ifdef DOUBLE_BATTERY
if (battery2 == nullptr) {
#ifdef BMW_I3_BATTERY
battery2 =
new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer.system.status.battery2_allows_contactor_closing,
can_config.battery_double, WUP_PIN2);
#else
battery2 =
new SELECTED_BATTERY_CLASS(&datalayer.battery2, &datalayer.system.status.battery2_allows_contactor_closing,
nullptr, can_config.battery_double);
#endif
}
battery2->setup();
#endif

View file

@ -1,29 +1,12 @@
#include "../include.h"
#ifdef BMW_I3_BATTERY
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h"
#include "BMW-I3-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
static BatterySize detectedBattery = BATTERY_60AH;
static BatterySize detectedBattery2 = BATTERY_60AH; // For double battery setups
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
static CmdState cmdState = SOC;
const unsigned char crc8_table[256] =
{ // CRC8_SAE_J1850_ZER0 formula,0x1D Poly,initial value 0x3F,Final XOR value varies
@ -43,312 +26,6 @@ const unsigned char crc8_table[256] =
0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, 0x97, 0x8A, 0xAD, 0xB0,
0xE3, 0xFE, 0xD9, 0xC4};
/* CAN messages from PT-CAN2 not needed to operate the battery
0AA 105 13D 0BB 0AD 0A5 150 100 1A1 10E 153 197 429 1AA 12F 59A 2E3 2BE 211 2b3 3FD 2E8 2B7 108 29D 29C 29B 2C0 330
3E9 32F 19E 326 55E 515 509 50A 51A 2F5 3A4 432 3C9
*/
CAN_frame BMW_10B = {.FD = false,
.ext_ID = false,
.DLC = 3,
.ID = 0x10B,
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command
CAN_frame BMW_12F = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x12F,
.data = {0xE6, 0x24, 0x86, 0x1A, 0xF1, 0x31, 0x30, 0x00}}; //0x12F Wakeup VCU
CAN_frame BMW_13E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x13E,
.data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
CAN_frame BMW_192 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x192,
.data = {0xFF, 0xFF, 0xA3, 0x8F, 0x93, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_19B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x19B,
.data = {0x20, 0x40, 0x40, 0x55, 0xFD, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_1D0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1D0,
.data = {0x4D, 0xF0, 0xAE, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_2CA = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x2CA, .data = {0x57, 0x57}};
CAN_frame BMW_2E2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2E2,
.data = {0x4F, 0xDB, 0x7F, 0xB9, 0x07, 0x51, 0xff, 0x00}};
CAN_frame BMW_30B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x30B,
.data = {0xe1, 0xf0, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff}};
CAN_frame BMW_328 = {.FD = false, .ext_ID = false, .DLC = 6, .ID = 0x328, .data = {0xB0, 0xE4, 0x87, 0x0E, 0x30, 0x22}};
CAN_frame BMW_37B = {.FD = false, .ext_ID = false, .DLC = 6, .ID = 0x37B, .data = {0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00}};
CAN_frame BMW_380 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x380,
.data = {0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34}};
CAN_frame BMW_3A0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3A0,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}};
CAN_frame BMW_3A7 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x3A7,
.data = {0x05, 0xF5, 0x0A, 0x00, 0x4F, 0x11, 0xF0}};
CAN_frame BMW_3C5 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3C5,
.data = {0x30, 0x05, 0x47, 0x70, 0x2c, 0xce, 0xc3, 0x34}};
CAN_frame BMW_3CA = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3CA,
.data = {0x87, 0x80, 0x30, 0x0C, 0x0C, 0x81, 0xFF, 0xFF}};
CAN_frame BMW_3D0 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3D0, .data = {0xFD, 0xFF}};
CAN_frame BMW_3E4 = {.FD = false, .ext_ID = false, .DLC = 6, .ID = 0x3E4, .data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}};
CAN_frame BMW_3E5 = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3E5, .data = {0xFC, 0xFF, 0xFF}};
CAN_frame BMW_3E8 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3E8, .data = {0xF0, 0xFF}}; //1000ms OBD reset
CAN_frame BMW_3EC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3EC,
.data = {0xF5, 0x10, 0x00, 0x00, 0x80, 0x25, 0x0F, 0xFC}};
CAN_frame BMW_3F9 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3F9,
.data = {0xA7, 0x2A, 0x00, 0xE2, 0xA6, 0x30, 0xC3, 0xFF}};
CAN_frame BMW_3FB = {.FD = false, .ext_ID = false, .DLC = 6, .ID = 0x3FB, .data = {0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x00}};
CAN_frame BMW_3FC = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3FC, .data = {0xC0, 0xF9, 0x0F}};
CAN_frame BMW_418 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x418,
.data = {0xFF, 0x7C, 0xFF, 0x00, 0xC0, 0x3F, 0xFF, 0xFF}};
CAN_frame BMW_41D = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x41D, .data = {0xFF, 0xF7, 0x7F, 0xFF}};
CAN_frame BMW_433 = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x433,
.data = {0xFF, 0x00, 0x0F, 0xFF}}; // HV specification
CAN_frame BMW_512 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x512,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; // 0x512 Network management
CAN_frame BMW_592_0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x592,
.data = {0x86, 0x10, 0x07, 0x21, 0x6e, 0x35, 0x5e, 0x86}};
CAN_frame BMW_592_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x592,
.data = {0x86, 0x21, 0xb4, 0xdd, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BMW_5F8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5F8,
.data = {0x64, 0x01, 0x00, 0x0B, 0x92, 0x03, 0x00, 0x05}};
CAN_frame BMW_6F1_CELL = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBF}};
CAN_frame BMW_6F1_SOH = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0x63, 0x35}};
CAN_frame BMW_6F1_SOC = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}};
CAN_frame BMW_6F1_CELL_VOLTAGE_AVG = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDF, 0xA0}};
CAN_frame BMW_6F1_CONTINUE = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6F1, .data = {0x07, 0x30, 0x00, 0x02}};
CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x6F4,
.data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}};
CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6E}};
//The above CAN messages need to be sent towards the battery to keep it alive
static uint8_t startup_counter_contactor = 0;
static uint8_t alive_counter_20ms = 0;
static uint8_t alive_counter_100ms = 0;
static uint8_t alive_counter_200ms = 0;
static uint8_t alive_counter_500ms = 0;
static uint8_t alive_counter_1000ms = 0;
static uint8_t alive_counter_5000ms = 0;
static uint8_t BMW_1D0_counter = 0;
static uint8_t BMW_13E_counter = 0;
static uint8_t BMW_380_counter = 0;
static uint32_t BMW_328_seconds = 243785948; // Initialized to make the battery think vehicle was made 7.7years ago
static uint16_t BMW_328_days =
9244; //Time since 1.1.2000. Hacky implementation to make it think current date is 23rd April 2025
static uint32_t BMS_328_seconds_to_day = 0; //Counter to keep track of days uptime
static bool battery_awake = false;
static bool battery2_awake = false;
static bool battery_info_available = false;
static bool battery2_info_available = false;
static bool skipCRCCheck = false;
static bool CRCCheckPassedPreviously = false;
static bool skipCRCCheck_battery2 = false;
static bool CRCCheckPassedPreviously_battery2 = false;
static uint16_t cellvoltage_temp_mV = 0;
static uint32_t battery_serial_number = 0;
static uint32_t battery_available_power_shortterm_charge = 0;
static uint32_t battery_available_power_shortterm_discharge = 0;
static uint32_t battery_available_power_longterm_charge = 0;
static uint32_t battery_available_power_longterm_discharge = 0;
static uint32_t battery_BEV_available_power_shortterm_charge = 0;
static uint32_t battery_BEV_available_power_shortterm_discharge = 0;
static uint32_t battery_BEV_available_power_longterm_charge = 0;
static uint32_t battery_BEV_available_power_longterm_discharge = 0;
static uint16_t battery_energy_content_maximum_Wh = 0;
static uint16_t battery_display_SOC = 0;
static uint16_t battery_volts = 0;
static uint16_t battery_HVBatt_SOC = 0;
static uint16_t battery_DC_link_voltage = 0;
static uint16_t battery_max_charge_voltage = 0;
static uint16_t battery_min_discharge_voltage = 0;
static uint16_t battery_predicted_energy_charge_condition = 0;
static uint16_t battery_predicted_energy_charging_target = 0;
static uint16_t battery_actual_value_power_heating = 0; //0 - 4094 W
static uint16_t battery_prediction_voltage_shortterm_charge = 0;
static uint16_t battery_prediction_voltage_shortterm_discharge = 0;
static uint16_t battery_prediction_voltage_longterm_charge = 0;
static uint16_t battery_prediction_voltage_longterm_discharge = 0;
static uint16_t battery_prediction_duration_charging_minutes = 0;
static uint16_t battery_target_voltage_in_CV_mode = 0;
static uint16_t battery_soc = 0;
static uint16_t battery_soc_hvmax = 0;
static uint16_t battery_soc_hvmin = 0;
static uint16_t battery_capacity_cah = 0;
static int16_t battery_temperature_HV = 0;
static int16_t battery_temperature_heat_exchanger = 0;
static int16_t battery_temperature_max = 0;
static int16_t battery_temperature_min = 0;
static int16_t battery_max_charge_amperage = 0;
static int16_t battery_max_discharge_amperage = 0;
static int16_t battery_current = 0;
static uint8_t battery_status_error_isolation_external_Bordnetz = 0;
static uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
static uint8_t battery_request_cooling = 0;
static uint8_t battery_status_valve_cooling = 0;
static uint8_t battery_status_error_locking = 0;
static uint8_t battery_status_precharge_locked = 0;
static uint8_t battery_status_disconnecting_switch = 0;
static uint8_t battery_status_emergency_mode = 0;
static uint8_t battery_request_service = 0;
static uint8_t battery_error_emergency_mode = 0;
static uint8_t battery_status_error_disconnecting_switch = 0;
static uint8_t battery_status_warning_isolation = 0;
static uint8_t battery_status_cold_shutoff_valve = 0;
static uint8_t battery_request_open_contactors = 0;
static uint8_t battery_request_open_contactors_instantly = 0;
static uint8_t battery_request_open_contactors_fast = 0;
static uint8_t battery_charging_condition_delta = 0;
static uint8_t battery_status_service_disconnection_plug = 0;
static uint8_t battery_status_measurement_isolation = 0;
static uint8_t battery_request_abort_charging = 0;
static uint8_t battery_prediction_time_end_of_charging_minutes = 0;
static uint8_t battery_request_operating_mode = 0;
static uint8_t battery_request_charging_condition_minimum = 0;
static uint8_t battery_request_charging_condition_maximum = 0;
static uint8_t battery_status_cooling_HV = 0; //1 works, 2 does not start
static uint8_t battery_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection function error, 2 diag not yet expired
static uint8_t battery_status_diagnosis_powertrain_maximum_multiplexer = 0;
static uint8_t battery_status_diagnosis_powertrain_immediate_multiplexer = 0;
static uint8_t battery_ID2 = 0;
static uint8_t battery_soh = 99;
static uint16_t cellvoltage2_temp_mV = 0;
static uint32_t battery2_serial_number = 0;
static uint32_t battery2_available_power_shortterm_charge = 0;
static uint32_t battery2_available_power_shortterm_discharge = 0;
static uint32_t battery2_available_power_longterm_charge = 0;
static uint32_t battery2_available_power_longterm_discharge = 0;
static uint32_t battery2_BEV_available_power_shortterm_charge = 0;
static uint32_t battery2_BEV_available_power_shortterm_discharge = 0;
static uint32_t battery2_BEV_available_power_longterm_charge = 0;
static uint32_t battery2_BEV_available_power_longterm_discharge = 0;
static uint16_t battery2_energy_content_maximum_Wh = 0;
static uint16_t battery2_display_SOC = 0;
static uint16_t battery2_volts = 0;
static uint16_t battery2_HVBatt_SOC = 0;
static uint16_t battery2_DC_link_voltage = 0;
static uint16_t battery2_max_charge_voltage = 0;
static uint16_t battery2_min_discharge_voltage = 0;
static uint16_t battery2_predicted_energy_charge_condition = 0;
static uint16_t battery2_predicted_energy_charging_target = 0;
static uint16_t battery2_actual_value_power_heating = 0; //0 - 4094 W
static uint16_t battery2_prediction_voltage_shortterm_charge = 0;
static uint16_t battery2_prediction_voltage_shortterm_discharge = 0;
static uint16_t battery2_prediction_voltage_longterm_charge = 0;
static uint16_t battery2_prediction_voltage_longterm_discharge = 0;
static uint16_t battery2_prediction_duration_charging_minutes = 0;
static uint16_t battery2_target_voltage_in_CV_mode = 0;
static uint16_t battery2_soc = 0;
static uint16_t battery2_soc_hvmax = 0;
static uint16_t battery2_soc_hvmin = 0;
static uint16_t battery2_capacity_cah = 0;
static int16_t battery2_temperature_HV = 0;
static int16_t battery2_temperature_heat_exchanger = 0;
static int16_t battery2_temperature_max = 0;
static int16_t battery2_temperature_min = 0;
static int16_t battery2_max_charge_amperage = 0;
static int16_t battery2_max_discharge_amperage = 0;
static int16_t battery2_current = 0;
static uint8_t battery2_status_error_isolation_external_Bordnetz = 0;
static uint8_t battery2_status_error_isolation_internal_Bordnetz = 0;
static uint8_t battery2_request_cooling = 0;
static uint8_t battery2_status_valve_cooling = 0;
static uint8_t battery2_status_error_locking = 0;
static uint8_t battery2_status_precharge_locked = 0;
static uint8_t battery2_status_disconnecting_switch = 0;
static uint8_t battery2_status_emergency_mode = 0;
static uint8_t battery2_request_service = 0;
static uint8_t battery2_error_emergency_mode = 0;
static uint8_t battery2_status_error_disconnecting_switch = 0;
static uint8_t battery2_status_warning_isolation = 0;
static uint8_t battery2_status_cold_shutoff_valve = 0;
static uint8_t battery2_request_open_contactors = 0;
static uint8_t battery2_request_open_contactors_instantly = 0;
static uint8_t battery2_request_open_contactors_fast = 0;
static uint8_t battery2_charging_condition_delta = 0;
static uint8_t battery2_status_service_disconnection_plug = 0;
static uint8_t battery2_status_measurement_isolation = 0;
static uint8_t battery2_request_abort_charging = 0;
static uint8_t battery2_prediction_time_end_of_charging_minutes = 0;
static uint8_t battery2_request_operating_mode = 0;
static uint8_t battery2_request_charging_condition_minimum = 0;
static uint8_t battery2_request_charging_condition_maximum = 0;
static uint8_t battery2_status_cooling_HV = 0; //1 works, 2 does not start
static uint8_t battery2_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection function error, 2 diag not yet expired
static uint8_t battery2_status_diagnosis_powertrain_maximum_multiplexer = 0;
static uint8_t battery2_status_diagnosis_powertrain_immediate_multiplexer = 0;
static uint8_t battery2_ID2 = 0;
static uint8_t battery2_soh = 99;
static uint8_t message_data[50];
static uint8_t next_data = 0;
static uint8_t current_cell_polled = 0;
static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
uint8_t crc = initial_value;
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
@ -365,118 +42,54 @@ static uint8_t increment_alive_counter(uint8_t counter) {
return counter;
}
void update_values_battery2() { //This function maps all the values fetched via CAN2 to the battery2 datalayer
if (!battery2_awake) {
return;
}
datalayer.battery2.status.real_soc = (battery2_display_SOC * 50);
datalayer.battery2.status.voltage_dV = battery2_volts; //Unit V+1 (5000 = 500.0V)
datalayer.battery2.status.current_dA = battery2_current;
datalayer.battery2.info.total_capacity_Wh = battery2_energy_content_maximum_Wh;
datalayer.battery2.status.remaining_capacity_Wh = battery2_predicted_energy_charge_condition;
datalayer.battery2.status.soh_pptt = battery2_soh * 100;
datalayer.battery2.status.max_discharge_power_W = battery2_BEV_available_power_longterm_discharge;
datalayer.battery2.status.max_charge_power_W = battery2_BEV_available_power_longterm_charge;
datalayer.battery2.status.temperature_min_dC = battery2_temperature_min * 10; // Add a decimal
datalayer.battery2.status.temperature_max_dC = battery2_temperature_max * 10; // Add a decimal
if (battery2_info_available) {
// Start checking safeties. First up, cellvoltages!
if (detectedBattery2 == BATTERY_60AH) {
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_60AH;
datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_60AH;
} else if (detectedBattery2 == BATTERY_94AH) {
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94AH;
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94AH;
datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_94AH;
datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_94AH;
} else { // BATTERY_120AH
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_120AH;
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_120AH;
datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_120AH;
datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_120AH;
}
}
// Perform other safety checks
if (battery2_status_error_locking == 2) { // HVIL seated?
set_event(EVENT_HVIL_FAILURE, 2);
} else {
clear_event(EVENT_HVIL_FAILURE);
}
if (battery2_status_error_disconnecting_switch > 0) { // Check if contactors are sticking / welded
set_event(EVENT_CONTACTOR_WELDED, 0);
} else {
clear_event(EVENT_CONTACTOR_WELDED);
}
}
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
void BmwI3Battery::update_values() { //This function maps all the values fetched via CAN to the battery datalayer
if (datalayer.system.settings.equipment_stop_active == true) {
digitalWrite(WUP_PIN1, LOW); // Turn off WUP_PIN1
#if defined(WUP_PIN2) && defined(DOUBLE_BATTERY)
digitalWrite(WUP_PIN2, LOW); // Turn off WUP_PIN2
#endif // defined(WUP_PIN2) && defined (DOUBLE_BATTERY)
digitalWrite(wakeup_pin, LOW); // Turn off wakeup pin
} else {
digitalWrite(WUP_PIN1, HIGH); // Wake up the battery
#if defined(WUP_PIN2) && defined(DOUBLE_BATTERY)
digitalWrite(WUP_PIN2, HIGH); // Wake up the battery2
#endif // defined(WUP_PIN2) && defined (DOUBLE_BATTERY)
digitalWrite(wakeup_pin, HIGH); // Wake up the battery
}
if (!battery_awake) {
return;
}
datalayer.battery.status.real_soc = (battery_display_SOC * 50);
datalayer_battery->status.real_soc = (battery_display_SOC * 50);
datalayer.battery.status.voltage_dV = battery_volts; //Unit V+1 (5000 = 500.0V)
datalayer_battery->status.voltage_dV = battery_volts; //Unit V+1 (5000 = 500.0V)
datalayer.battery.status.current_dA = battery_current;
datalayer_battery->status.current_dA = battery_current;
datalayer.battery.info.total_capacity_Wh = battery_energy_content_maximum_Wh;
datalayer_battery->info.total_capacity_Wh = battery_energy_content_maximum_Wh;
datalayer.battery.status.remaining_capacity_Wh = battery_predicted_energy_charge_condition;
datalayer_battery->status.remaining_capacity_Wh = battery_predicted_energy_charge_condition;
datalayer.battery.status.soh_pptt = battery_soh * 100;
datalayer_battery->status.soh_pptt = battery_soh * 100;
datalayer.battery.status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge;
datalayer_battery->status.max_discharge_power_W = battery_BEV_available_power_longterm_discharge;
datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
datalayer_battery->status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
datalayer.battery.status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal
datalayer_battery->status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal
datalayer.battery.status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal
datalayer_battery->status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal
if (battery_info_available) {
// Start checking safeties. First up, cellvoltages!
if (detectedBattery == BATTERY_60AH) {
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_60AH;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_60AH;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_60AH;
datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_60AH;
} else if (detectedBattery == BATTERY_94AH) {
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94AH;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94AH;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_94AH;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_94AH;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_94AH;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_94AH;
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_94AH;
datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_94AH;
} else { // BATTERY_120AH
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_120AH;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_120AH;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_120AH;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_120AH;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_120AH;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_120AH;
datalayer_battery->info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_120AH;
datalayer_battery->info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_120AH;
}
}
@ -508,15 +121,15 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.bmwi3.ST_cold_shutoff_valve = battery_status_cold_shutoff_valve;
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
void BmwI3Battery::handle_incoming_can_frame(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
battery_awake = true;
datalayer.battery.status.CAN_battery_still_alive =
datalayer_battery->status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
battery_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A)
battery_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
datalayer.battery.status.voltage_dV = battery_volts; // Update the datalayer as soon as possible with this info
datalayer_battery->status.voltage_dV = battery_volts; // Update the datalayer as soon as possible with this info
battery_HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8 | rx_frame.data.u8[4]);
battery_request_open_contactors = (rx_frame.data.u8[5] & 0xC0) >> 6;
battery_request_open_contactors_instantly = (rx_frame.data.u8[6] & 0x03);
@ -552,7 +165,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
if (!skipCRCCheck) {
if (calculateCRC(rx_frame, rx_frame.DLC, 0x15) != rx_frame.data.u8[0]) {
// If calculated CRC does not match transmitted CRC, increase CANerror counter
datalayer.battery.status.CAN_error_counter++;
datalayer_battery->status.CAN_error_counter++;
// If the CRC check has never passed before, set the flag to skip future checks. Some SMEs have differing CRC checks.
if (!CRCCheckPassedPreviously) {
@ -638,10 +251,10 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
case 0x607: //BMS - responses to message requests on 0x615
if ((cmdState == CELL_VOLTAGE_CELLNO || cmdState == CELL_VOLTAGE_CELLNO_LAST) && (rx_frame.data.u8[0] == 0xF4)) {
if (rx_frame.DLC == 6) {
transmit_can_frame(&BMW_6F4_CELL_CONTINUE, can_config.battery); // tell battery to send the cellvoltage
transmit_can_frame(&BMW_6F4_CELL_CONTINUE, can_interface); // tell battery to send the cellvoltage
}
if (rx_frame.DLC == 8) { // We have the full value, map it
datalayer.battery.status.cell_voltages_mV[current_cell_polled - 1] =
datalayer_battery->status.cell_voltages_mV[current_cell_polled - 1] =
(rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
}
}
@ -651,7 +264,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
while (count < rx_frame.DLC && next_data < 49) {
message_data[next_data++] = rx_frame.data.u8[count++];
}
transmit_can_frame(&BMW_6F1_CONTINUE, can_config.battery); // tell battery to send additional messages
transmit_can_frame(&BMW_6F1_CONTINUE, can_interface); // tell battery to send additional messages
} else if (rx_frame.DLC > 3 && next_data > 0 && rx_frame.data.u8[0] == 0xf1 &&
((rx_frame.data.u8[1] & 0xF0) == 0x20)) {
@ -665,11 +278,11 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
if (next_data >= 4) {
cellvoltage_temp_mV = (message_data[0] << 8 | message_data[1]);
if (cellvoltage_temp_mV < 4500) { // Prevents garbage data from being read on bootup
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_temp_mV;
datalayer_battery->status.cell_min_voltage_mV = cellvoltage_temp_mV;
}
cellvoltage_temp_mV = (message_data[2] << 8 | message_data[3]);
if (cellvoltage_temp_mV < 4500) { // Prevents garbage data from being read on bootup
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_temp_mV;
datalayer_battery->status.cell_max_voltage_mV = cellvoltage_temp_mV;
}
}
break;
@ -693,194 +306,8 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
break;
}
}
void handle_incoming_can_frame_battery2(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
battery2_awake = true;
datalayer.battery2.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
battery2_current = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) - 8192; //deciAmps (-819.2 to 819.0A)
battery2_volts = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]); //500.0 V
datalayer.battery2.status.voltage_dV =
battery2_volts; // Update the datalayer as soon as possible with this info, needed for contactor control
battery2_HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8 | rx_frame.data.u8[4]);
battery2_request_open_contactors = (rx_frame.data.u8[5] & 0xC0) >> 6;
battery2_request_open_contactors_instantly = (rx_frame.data.u8[6] & 0x03);
battery2_request_open_contactors_fast = (rx_frame.data.u8[6] & 0x0C) >> 2;
battery2_charging_condition_delta = (rx_frame.data.u8[6] & 0xF0) >> 4;
battery2_DC_link_voltage = rx_frame.data.u8[7];
break;
case 0x1FA: //BMS [1000ms] Status Of High-Voltage Battery - 1
battery2_status_error_isolation_external_Bordnetz = (rx_frame.data.u8[0] & 0x03);
battery2_status_error_isolation_internal_Bordnetz = (rx_frame.data.u8[0] & 0x0C) >> 2;
battery2_request_cooling = (rx_frame.data.u8[0] & 0x30) >> 4;
battery2_status_valve_cooling = (rx_frame.data.u8[0] & 0xC0) >> 6;
battery2_status_error_locking = (rx_frame.data.u8[1] & 0x03);
battery2_status_precharge_locked = (rx_frame.data.u8[1] & 0x0C) >> 2;
battery2_status_disconnecting_switch = (rx_frame.data.u8[1] & 0x30) >> 4;
battery2_status_emergency_mode = (rx_frame.data.u8[1] & 0xC0) >> 6;
battery2_request_service = (rx_frame.data.u8[2] & 0x03);
battery2_error_emergency_mode = (rx_frame.data.u8[2] & 0x0C) >> 2;
battery2_status_error_disconnecting_switch = (rx_frame.data.u8[2] & 0x30) >> 4;
battery2_status_warning_isolation = (rx_frame.data.u8[2] & 0xC0) >> 6;
battery2_status_cold_shutoff_valve = (rx_frame.data.u8[3] & 0x0F);
battery2_temperature_HV = (rx_frame.data.u8[4] - 50);
battery2_temperature_heat_exchanger = (rx_frame.data.u8[5] - 50);
battery2_temperature_min = (rx_frame.data.u8[6] - 50);
battery2_temperature_max = (rx_frame.data.u8[7] - 50);
break;
case 0x239: //BMS [200ms]
battery2_predicted_energy_charge_condition = (rx_frame.data.u8[2] << 8 | rx_frame.data.u8[1]); //Wh
battery2_predicted_energy_charging_target = ((rx_frame.data.u8[4] << 8 | rx_frame.data.u8[3]) * 0.02); //kWh
break;
case 0x2BD: //BMS [100ms] Status diagnosis high voltage - 1
battery2_awake = true;
if (!skipCRCCheck_battery2) {
if (calculateCRC(rx_frame, rx_frame.DLC, 0x15) != rx_frame.data.u8[0]) {
// If calculated CRC does not match transmitted CRC, increase CANerror counter
datalayer.battery2.status.CAN_error_counter++;
// If the CRC check has never passed before, set the flag to skip future checks. Some SMEs have differing CRC checks.
if (!CRCCheckPassedPreviously_battery2) {
skipCRCCheck_battery2 = true;
}
break;
} else {
// If CRC check passes, update the flag
CRCCheckPassedPreviously_battery2 = true;
}
}
// Process the data since CRC check is either passed or skipped
battery2_status_diagnostics_HV = (rx_frame.data.u8[2] & 0x0F);
break;
case 0x2F5: //BMS [100ms] High-Voltage Battery Charge/Discharge Limitations
battery2_max_charge_voltage = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
battery2_max_charge_amperage = (((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) - 819.2);
battery2_min_discharge_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
battery2_max_discharge_amperage = (((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) - 819.2);
break;
case 0x2FF: //BMS [100ms] Status Heating High-Voltage Battery
battery2_awake = true;
battery2_actual_value_power_heating = (rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4);
break;
case 0x363: //BMS [1s] Identification High-Voltage Battery
battery2_serial_number =
(rx_frame.data.u8[3] << 24 | rx_frame.data.u8[2] << 16 | rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
break;
case 0x3C2: //BMS (94AH exclusive) - Status diagnostics OBD 2 powertrain
battery2_status_diagnosis_powertrain_maximum_multiplexer =
((rx_frame.data.u8[1] & 0x03) << 4 | rx_frame.data.u8[0] >> 4);
battery2_status_diagnosis_powertrain_immediate_multiplexer = (rx_frame.data.u8[0] & 0xFC) >> 2;
break;
case 0x3EB: //BMS [1s] Status of charging high-voltage storage - 3
battery2_available_power_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) * 3;
battery2_available_power_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]) * 3;
battery2_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3;
battery2_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3;
break;
case 0x40D: //BMS [1s] Charging status of high-voltage storage - 1
battery2_BEV_available_power_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]) * 3;
battery2_BEV_available_power_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]) * 3;
battery2_BEV_available_power_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]) * 3;
battery2_BEV_available_power_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]) * 3;
break;
case 0x41C: //BMS [1s] Operating Mode Status Of Hybrid - 2
battery2_status_cooling_HV = (rx_frame.data.u8[1] & 0x03);
break;
case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2
battery2_prediction_voltage_shortterm_charge = (rx_frame.data.u8[1] << 8 | rx_frame.data.u8[0]);
battery2_prediction_voltage_shortterm_discharge = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
battery2_prediction_voltage_longterm_charge = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[4]);
battery2_prediction_voltage_longterm_discharge = (rx_frame.data.u8[7] << 8 | rx_frame.data.u8[6]);
break;
case 0x431: //BMS [200ms] Data High-Voltage Battery Unit
battery2_status_service_disconnection_plug = (rx_frame.data.u8[0] & 0x0F);
battery2_status_measurement_isolation = (rx_frame.data.u8[0] & 0x0C) >> 2;
battery2_request_abort_charging = (rx_frame.data.u8[0] & 0x30) >> 4;
battery2_prediction_duration_charging_minutes = (rx_frame.data.u8[3] << 8 | rx_frame.data.u8[2]);
battery2_prediction_time_end_of_charging_minutes = rx_frame.data.u8[4];
battery2_energy_content_maximum_Wh = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[5]) * 20;
if (battery2_energy_content_maximum_Wh > 33000) {
detectedBattery2 = BATTERY_120AH;
} else if (battery2_energy_content_maximum_Wh > 20000) {
detectedBattery2 = BATTERY_94AH;
} else {
detectedBattery2 = BATTERY_60AH;
}
break;
case 0x432: //BMS [200ms] SOC% info
battery2_request_operating_mode = (rx_frame.data.u8[0] & 0x03);
battery2_target_voltage_in_CV_mode = ((rx_frame.data.u8[1] << 4 | rx_frame.data.u8[0] >> 4)) / 10;
battery2_request_charging_condition_minimum = (rx_frame.data.u8[2] / 2);
battery2_request_charging_condition_maximum = (rx_frame.data.u8[3] / 2);
battery2_display_SOC = rx_frame.data.u8[4];
break;
case 0x507: //BMS [640ms] Network Management - 2 - This message is sent on the bus for sleep coordination purposes
break;
case 0x587: //BMS [5s] Services
battery2_ID2 = rx_frame.data.u8[0];
break;
case 0x607: //BMS - responses to message requests on 0x615
if ((cmdState == CELL_VOLTAGE_CELLNO || cmdState == CELL_VOLTAGE_CELLNO_LAST) && (rx_frame.data.u8[0] == 0xF4)) {
if (rx_frame.DLC == 6) {
transmit_can_frame(&BMW_6F4_CELL_CONTINUE,
can_config.battery_double); // tell battery to send the cellvoltage
}
if (rx_frame.DLC == 8) { // We have the full value, map it
datalayer.battery2.status.cell_voltages_mV[current_cell_polled - 1] =
(rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
}
}
if (rx_frame.DLC > 6 && next_data == 0 && rx_frame.data.u8[0] == 0xf1) {
uint8_t count2 = 6;
while (count2 < rx_frame.DLC && next_data < 49) {
message_data[next_data++] = rx_frame.data.u8[count2++];
}
transmit_can_frame(&BMW_6F1_CONTINUE, can_config.battery_double);
} else if (rx_frame.DLC > 3 && next_data > 0 && rx_frame.data.u8[0] == 0xf1 &&
((rx_frame.data.u8[1] & 0xF0) == 0x20)) {
uint8_t count2 = 2;
while (count2 < rx_frame.DLC && next_data < 49) {
message_data[next_data++] = rx_frame.data.u8[count2++];
}
switch (cmdState) {
case CELL_VOLTAGE_MINMAX:
if (next_data >= 4) {
cellvoltage2_temp_mV = (message_data[0] << 8 | message_data[1]);
if (cellvoltage2_temp_mV < 4500) { // Prevents garbage data from being read on bootup
datalayer.battery2.status.cell_min_voltage_mV = cellvoltage2_temp_mV;
}
cellvoltage2_temp_mV = (message_data[2] << 8 | message_data[3]);
if (cellvoltage_temp_mV < 4500) { // Prevents garbage data from being read on bootup
datalayer.battery2.status.cell_max_voltage_mV = cellvoltage2_temp_mV;
}
}
break;
case SOH:
if (next_data >= 4) {
battery2_soh = message_data[3];
battery2_info_available = true;
}
break;
case SOC:
if (next_data >= 6) {
battery2_soc = (message_data[0] << 8 | message_data[1]);
battery2_soc_hvmax = (message_data[2] << 8 | message_data[3]);
battery2_soc_hvmin = (message_data[4] << 8 | message_data[5]);
}
break;
}
}
break;
default:
break;
}
}
void transmit_can_battery(unsigned long currentMillis) {
void BmwI3Battery::transmit_can(unsigned long currentMillis) {
if (battery_awake) {
//Send 20ms message
@ -901,17 +328,11 @@ void transmit_can_battery(unsigned long currentMillis) {
BMW_13E_counter++;
BMW_13E.data.u8[4] = BMW_13E_counter;
if (datalayer.battery.status.bms_status == FAULT) {
if (datalayer_battery->status.bms_status == FAULT) {
} //If battery is not in Fault mode, allow contactor to close by sending 10B
else {
transmit_can_frame(&BMW_10B, can_config.battery);
else if (*allows_contactor_closing == true) {
transmit_can_frame(&BMW_10B, can_interface);
}
#ifdef DOUBLE_BATTERY //If second battery is allowed to join in, also send 10B
if (datalayer.system.status.battery2_allows_contactor_closing == true) {
transmit_can_frame(&BMW_10B, can_config.battery_double);
}
#endif
}
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
@ -922,10 +343,7 @@ void transmit_can_battery(unsigned long currentMillis) {
alive_counter_100ms = increment_alive_counter(alive_counter_100ms);
transmit_can_frame(&BMW_12F, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_12F, can_config.battery_double);
#endif
transmit_can_frame(&BMW_12F, can_interface);
}
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
@ -936,10 +354,7 @@ void transmit_can_battery(unsigned long currentMillis) {
alive_counter_200ms = increment_alive_counter(alive_counter_200ms);
transmit_can_frame(&BMW_19B, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_19B, can_config.battery_double);
#endif
transmit_can_frame(&BMW_19B, can_interface);
}
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
@ -950,21 +365,14 @@ void transmit_can_battery(unsigned long currentMillis) {
alive_counter_500ms = increment_alive_counter(alive_counter_500ms);
transmit_can_frame(&BMW_30B, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_30B, can_config.battery_double);
#endif
transmit_can_frame(&BMW_30B, can_interface);
}
// Send 640ms CAN Message
if (currentMillis - previousMillis640 >= INTERVAL_640_MS) {
previousMillis640 = currentMillis;
transmit_can_frame(&BMW_512, can_config.battery); // Keep BMS alive
transmit_can_frame(&BMW_5F8, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_512, can_config.battery_double);
transmit_can_frame(&BMW_5F8, can_config.battery_double);
#endif
transmit_can_frame(&BMW_512, can_interface); // Keep BMS alive
transmit_can_frame(&BMW_5F8, can_interface);
}
// Send 1000ms CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
@ -1001,40 +409,22 @@ void transmit_can_battery(unsigned long currentMillis) {
alive_counter_1000ms = increment_alive_counter(alive_counter_1000ms);
transmit_can_frame(&BMW_3E8, can_config.battery); //Order comes from CAN logs
transmit_can_frame(&BMW_328, can_config.battery);
transmit_can_frame(&BMW_3F9, can_config.battery);
transmit_can_frame(&BMW_2E2, can_config.battery);
transmit_can_frame(&BMW_41D, can_config.battery);
transmit_can_frame(&BMW_3D0, can_config.battery);
transmit_can_frame(&BMW_3CA, can_config.battery);
transmit_can_frame(&BMW_3A7, can_config.battery);
transmit_can_frame(&BMW_2CA, can_config.battery);
transmit_can_frame(&BMW_3FB, can_config.battery);
transmit_can_frame(&BMW_418, can_config.battery);
transmit_can_frame(&BMW_1D0, can_config.battery);
transmit_can_frame(&BMW_3EC, can_config.battery);
transmit_can_frame(&BMW_192, can_config.battery);
transmit_can_frame(&BMW_13E, can_config.battery);
transmit_can_frame(&BMW_433, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_3E8, can_config.battery_double);
transmit_can_frame(&BMW_328, can_config.battery_double);
transmit_can_frame(&BMW_3F9, can_config.battery_double);
transmit_can_frame(&BMW_2E2, can_config.battery_double);
transmit_can_frame(&BMW_41D, can_config.battery_double);
transmit_can_frame(&BMW_3D0, can_config.battery_double);
transmit_can_frame(&BMW_3CA, can_config.battery_double);
transmit_can_frame(&BMW_3A7, can_config.battery_double);
transmit_can_frame(&BMW_2CA, can_config.battery_double);
transmit_can_frame(&BMW_3FB, can_config.battery_double);
transmit_can_frame(&BMW_418, can_config.battery_double);
transmit_can_frame(&BMW_1D0, can_config.battery_double);
transmit_can_frame(&BMW_3EC, can_config.battery_double);
transmit_can_frame(&BMW_192, can_config.battery_double);
transmit_can_frame(&BMW_13E, can_config.battery_double);
transmit_can_frame(&BMW_433, can_config.battery_double);
#endif
transmit_can_frame(&BMW_3E8, can_interface); //Order comes from CAN logs
transmit_can_frame(&BMW_328, can_interface);
transmit_can_frame(&BMW_3F9, can_interface);
transmit_can_frame(&BMW_2E2, can_interface);
transmit_can_frame(&BMW_41D, can_interface);
transmit_can_frame(&BMW_3D0, can_interface);
transmit_can_frame(&BMW_3CA, can_interface);
transmit_can_frame(&BMW_3A7, can_interface);
transmit_can_frame(&BMW_2CA, can_interface);
transmit_can_frame(&BMW_3FB, can_interface);
transmit_can_frame(&BMW_418, can_interface);
transmit_can_frame(&BMW_1D0, can_interface);
transmit_can_frame(&BMW_3EC, can_interface);
transmit_can_frame(&BMW_192, can_interface);
transmit_can_frame(&BMW_13E, can_interface);
transmit_can_frame(&BMW_433, can_interface);
BMW_433.data.u8[1] = 0x01; // First 433 message byte1 we send is unique, once we sent initial value send this
BMW_3E8.data.u8[0] = 0xF1; // First 3E8 message byte0 we send is unique, once we sent initial value send this
@ -1042,24 +432,15 @@ void transmit_can_battery(unsigned long currentMillis) {
next_data = 0;
switch (cmdState) {
case SOC:
transmit_can_frame(&BMW_6F1_CELL, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_6F1_CELL, can_config.battery_double);
#endif
transmit_can_frame(&BMW_6F1_CELL, can_interface);
cmdState = CELL_VOLTAGE_MINMAX;
break;
case CELL_VOLTAGE_MINMAX:
transmit_can_frame(&BMW_6F1_SOH, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_6F1_SOH, can_config.battery_double);
#endif
transmit_can_frame(&BMW_6F1_SOH, can_interface);
cmdState = SOH;
break;
case SOH:
transmit_can_frame(&BMW_6F1_CELL_VOLTAGE_AVG, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_6F1_CELL_VOLTAGE_AVG, can_config.battery_double);
#endif
transmit_can_frame(&BMW_6F1_CELL_VOLTAGE_AVG, can_interface);
cmdState = CELL_VOLTAGE_CELLNO;
current_cell_polled = 0;
@ -1072,17 +453,11 @@ void transmit_can_battery(unsigned long currentMillis) {
cmdState = CELL_VOLTAGE_CELLNO;
BMW_6F4_CELL_VOLTAGE_CELLNO.data.u8[6] = current_cell_polled;
transmit_can_frame(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_config.battery_double);
#endif
transmit_can_frame(&BMW_6F4_CELL_VOLTAGE_CELLNO, can_interface);
}
break;
case CELL_VOLTAGE_CELLNO_LAST:
transmit_can_frame(&BMW_6F1_SOC, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_6F1_SOC, can_config.battery_double);
#endif
transmit_can_frame(&BMW_6F1_SOC, can_interface);
cmdState = SOC;
break;
}
@ -1094,26 +469,16 @@ void transmit_can_battery(unsigned long currentMillis) {
BMW_3FC.data.u8[1] = ((BMW_3FC.data.u8[1] & 0xF0) + alive_counter_5000ms);
BMW_3C5.data.u8[0] = ((BMW_3C5.data.u8[0] & 0xF0) + alive_counter_5000ms);
transmit_can_frame(&BMW_3FC, can_config.battery); //Order comes from CAN logs
transmit_can_frame(&BMW_3C5, can_config.battery);
transmit_can_frame(&BMW_3A0, can_config.battery);
transmit_can_frame(&BMW_592_0, can_config.battery);
transmit_can_frame(&BMW_592_1, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_3FC, can_config.battery_double);
transmit_can_frame(&BMW_3C5, can_config.battery_double);
transmit_can_frame(&BMW_3A0, can_config.battery_double);
transmit_can_frame(&BMW_592_0, can_config.battery_double);
transmit_can_frame(&BMW_592_1, can_config.battery_double);
#endif
transmit_can_frame(&BMW_3FC, can_interface); //Order comes from CAN logs
transmit_can_frame(&BMW_3C5, can_interface);
transmit_can_frame(&BMW_3A0, can_interface);
transmit_can_frame(&BMW_592_0, can_interface);
transmit_can_frame(&BMW_592_1, can_interface);
alive_counter_5000ms = increment_alive_counter(alive_counter_5000ms);
if (BMW_380_counter < 3) {
transmit_can_frame(&BMW_380, can_config.battery); // This message stops after 3 times on startup
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_380, can_config.battery_double);
#endif
transmit_can_frame(&BMW_380, can_interface); // This message stops after 3 times on startup
BMW_380_counter++;
}
}
@ -1121,14 +486,9 @@ void transmit_can_battery(unsigned long currentMillis) {
if (currentMillis - previousMillis10000 >= INTERVAL_10_S) {
previousMillis10000 = currentMillis;
transmit_can_frame(&BMW_3E5, can_config.battery); //Order comes from CAN logs
transmit_can_frame(&BMW_3E4, can_config.battery);
transmit_can_frame(&BMW_37B, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can_frame(&BMW_3E5, can_config.battery_double);
transmit_can_frame(&BMW_3E4, can_config.battery_double);
transmit_can_frame(&BMW_37B, can_config.battery_double);
#endif
transmit_can_frame(&BMW_3E5, can_interface); //Order comes from CAN logs
transmit_can_frame(&BMW_3E4, can_interface);
transmit_can_frame(&BMW_37B, can_interface);
BMW_3E5.data.u8[0] = 0xFD; // First 3E5 message byte0 we send is unique, once we sent initial value send this
}
@ -1144,30 +504,19 @@ void transmit_can_battery(unsigned long currentMillis) {
}
}
void setup_battery(void) { // Performs one time setup at startup
void BmwI3Battery::setup(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "BMW i3", 63);
datalayer.system.info.battery_protocol[63] = '\0';
//Before we have started up and detected which battery is in use, use 60AH values
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer_battery->info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
datalayer_battery->info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
datalayer_battery->info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = NUMBER_OF_CELLS;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
datalayer.battery2.status.voltage_dV =
0; //Init voltage to 0 to allow contactor check to operate without fear of default values colliding
datalayer.battery2.info.number_of_cells = NUMBER_OF_CELLS;
#endif
pinMode(WUP_PIN1, OUTPUT);
digitalWrite(WUP_PIN1, HIGH); // Wake up the battery
#if defined(DOUBLE_BATTERY) && defined(WUP_PIN2)
pinMode(WUP_PIN2, OUTPUT);
digitalWrite(WUP_PIN2, HIGH); // Wake up the battery
#endif // defined(WUP_PIN2) && defined (DOUBLE_BATTERY)
datalayer_battery->info.number_of_cells = NUMBER_OF_CELLS;
pinMode(wakeup_pin, OUTPUT);
digitalWrite(wakeup_pin, HIGH); // Wake up the battery
}
#endif

View file

@ -1,25 +1,334 @@
#ifndef BMW_I3_BATTERY_H
#define BMW_I3_BATTERY_H
#include <Arduino.h>
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../include.h"
#include "CanBattery.h"
#define BATTERY_SELECTED
#define SELECTED_BATTERY_CLASS BmwI3Battery
#define MAX_CELL_VOLTAGE_60AH 4110 // Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_60AH 2700 // Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_VOLTAGE_94AH 4140 // Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_94AH 2700 // Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_VOLTAGE_120AH 4190 // Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_120AH 2790 // Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 250 // LED turns yellow on the board if mv delta exceeds this value
#define MAX_PACK_VOLTAGE_60AH 3950 // Charge stops if pack voltage exceeds this value
#define MIN_PACK_VOLTAGE_60AH 2590 // Discharge stops if pack voltage exceeds this value
#define MAX_PACK_VOLTAGE_94AH 3980 // Charge stops if pack voltage exceeds this value
#define MIN_PACK_VOLTAGE_94AH 2590 // Discharge stops if pack voltage exceeds this value
#define MAX_PACK_VOLTAGE_120AH 4030 // Charge stops if pack voltage exceeds this value
#define MIN_PACK_VOLTAGE_120AH 2680 // Discharge stops if pack voltage exceeds this value
#define NUMBER_OF_CELLS 96
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
class BmwI3Battery : public CanBattery {
public:
// Use this constructor for the second battery.
BmwI3Battery(DATALAYER_BATTERY_TYPE* datalayer_ptr, bool* allows_contactor_closing_ptr, int targetCan, int wakeup) {
datalayer_battery = datalayer_ptr;
allows_contactor_closing = allows_contactor_closing_ptr;
can_interface = targetCan;
wakeup_pin = wakeup;
*allows_contactor_closing = true;
//Init voltage to 0 to allow contactor check to operate without fear of default values colliding
battery_volts = 0;
}
// Use the default constructor to create the first or single battery.
BmwI3Battery() {
datalayer_battery = &datalayer.battery;
allows_contactor_closing = &datalayer.system.status.battery_allows_contactor_closing;
can_interface = can_config.battery;
wakeup_pin = WUP_PIN1;
}
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);
private:
const int MAX_CELL_VOLTAGE_60AH = 4110; // Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_60AH = 2700; // Battery is put into emergency stop if one cell goes below this value
const int MAX_CELL_VOLTAGE_94AH = 4140; // Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_94AH = 2700; // Battery is put into emergency stop if one cell goes below this value
const int MAX_CELL_VOLTAGE_120AH = 4190; // Battery is put into emergency stop if one cell goes over this value
const int MIN_CELL_VOLTAGE_120AH = 2790; // Battery is put into emergency stop if one cell goes below this value
const int MAX_CELL_DEVIATION_MV = 250; // LED turns yellow on the board if mv delta exceeds this value
const int MAX_PACK_VOLTAGE_60AH = 3950; // Charge stops if pack voltage exceeds this value
const int MIN_PACK_VOLTAGE_60AH = 2590; // Discharge stops if pack voltage exceeds this value
const int MAX_PACK_VOLTAGE_94AH = 3980; // Charge stops if pack voltage exceeds this value
const int MIN_PACK_VOLTAGE_94AH = 2590; // Discharge stops if pack voltage exceeds this value
const int MAX_PACK_VOLTAGE_120AH = 4030; // Charge stops if pack voltage exceeds this value
const int MIN_PACK_VOLTAGE_120AH = 2680; // Discharge stops if pack voltage exceeds this value
const int NUMBER_OF_CELLS = 96;
DATALAYER_BATTERY_TYPE* datalayer_battery;
bool* allows_contactor_closing;
int wakeup_pin;
int can_interface;
unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
enum BatterySize { BATTERY_60AH, BATTERY_94AH, BATTERY_120AH };
BatterySize detectedBattery = BATTERY_60AH;
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
CmdState cmdState = SOC;
/* CAN messages from PT-CAN2 not needed to operate the battery
0AA 105 13D 0BB 0AD 0A5 150 100 1A1 10E 153 197 429 1AA 12F 59A 2E3 2BE 211 2b3 3FD 2E8 2B7 108 29D 29C 29B 2C0 330
3E9 32F 19E 326 55E 515 509 50A 51A 2F5 3A4 432 3C9
*/
CAN_frame BMW_10B = {.FD = false,
.ext_ID = false,
.DLC = 3,
.ID = 0x10B,
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command
CAN_frame BMW_12F = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x12F,
.data = {0xE6, 0x24, 0x86, 0x1A, 0xF1, 0x31, 0x30, 0x00}}; //0x12F Wakeup VCU
CAN_frame BMW_13E = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x13E,
.data = {0xFF, 0x31, 0xFA, 0xFA, 0xFA, 0xFA, 0x0C, 0x00}};
CAN_frame BMW_192 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x192,
.data = {0xFF, 0xFF, 0xA3, 0x8F, 0x93, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_19B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x19B,
.data = {0x20, 0x40, 0x40, 0x55, 0xFD, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_1D0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x1D0,
.data = {0x4D, 0xF0, 0xAE, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF}};
CAN_frame BMW_2CA = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x2CA, .data = {0x57, 0x57}};
CAN_frame BMW_2E2 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x2E2,
.data = {0x4F, 0xDB, 0x7F, 0xB9, 0x07, 0x51, 0xff, 0x00}};
CAN_frame BMW_30B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x30B,
.data = {0xe1, 0xf0, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xff}};
CAN_frame BMW_328 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x328,
.data = {0xB0, 0xE4, 0x87, 0x0E, 0x30, 0x22}};
CAN_frame BMW_37B = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x37B,
.data = {0x40, 0x00, 0x00, 0xFF, 0xFF, 0x00}};
CAN_frame BMW_380 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x380,
.data = {0x56, 0x5A, 0x37, 0x39, 0x34, 0x34, 0x34}};
CAN_frame BMW_3A0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3A0,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}};
CAN_frame BMW_3A7 = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x3A7,
.data = {0x05, 0xF5, 0x0A, 0x00, 0x4F, 0x11, 0xF0}};
CAN_frame BMW_3C5 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3C5,
.data = {0x30, 0x05, 0x47, 0x70, 0x2c, 0xce, 0xc3, 0x34}};
CAN_frame BMW_3CA = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3CA,
.data = {0x87, 0x80, 0x30, 0x0C, 0x0C, 0x81, 0xFF, 0xFF}};
CAN_frame BMW_3D0 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3D0, .data = {0xFD, 0xFF}};
CAN_frame BMW_3E4 = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x3E4,
.data = {0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}};
CAN_frame BMW_3E5 = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3E5, .data = {0xFC, 0xFF, 0xFF}};
CAN_frame BMW_3E8 = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x3E8, .data = {0xF0, 0xFF}}; //1000ms OBD reset
CAN_frame BMW_3EC = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3EC,
.data = {0xF5, 0x10, 0x00, 0x00, 0x80, 0x25, 0x0F, 0xFC}};
CAN_frame BMW_3F9 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x3F9,
.data = {0xA7, 0x2A, 0x00, 0xE2, 0xA6, 0x30, 0xC3, 0xFF}};
CAN_frame BMW_3FB = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x3FB,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x00}};
CAN_frame BMW_3FC = {.FD = false, .ext_ID = false, .DLC = 3, .ID = 0x3FC, .data = {0xC0, 0xF9, 0x0F}};
CAN_frame BMW_418 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x418,
.data = {0xFF, 0x7C, 0xFF, 0x00, 0xC0, 0x3F, 0xFF, 0xFF}};
CAN_frame BMW_41D = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x41D, .data = {0xFF, 0xF7, 0x7F, 0xFF}};
CAN_frame BMW_433 = {.FD = false,
.ext_ID = false,
.DLC = 4,
.ID = 0x433,
.data = {0xFF, 0x00, 0x0F, 0xFF}}; // HV specification
CAN_frame BMW_512 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x512,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12}}; // 0x512 Network management
CAN_frame BMW_592_0 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x592,
.data = {0x86, 0x10, 0x07, 0x21, 0x6e, 0x35, 0x5e, 0x86}};
CAN_frame BMW_592_1 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x592,
.data = {0x86, 0x21, 0xb4, 0xdd, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BMW_5F8 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x5F8,
.data = {0x64, 0x01, 0x00, 0x0B, 0x92, 0x03, 0x00, 0x05}};
CAN_frame BMW_6F1_CELL = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDD, 0xBF}};
CAN_frame BMW_6F1_SOH = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0x63, 0x35}};
CAN_frame BMW_6F1_SOC = {.FD = false, .ext_ID = false, .DLC = 5, .ID = 0x6F1, .data = {0x07, 0x03, 0x22, 0xDD, 0xBC}};
CAN_frame BMW_6F1_CELL_VOLTAGE_AVG = {.FD = false,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F1,
.data = {0x07, 0x03, 0x22, 0xDF, 0xA0}};
CAN_frame BMW_6F1_CONTINUE = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x6F1, .data = {0x07, 0x30, 0x00, 0x02}};
CAN_frame BMW_6F4_CELL_VOLTAGE_CELLNO = {.FD = false,
.ext_ID = false,
.DLC = 7,
.ID = 0x6F4,
.data = {0x07, 0x05, 0x31, 0x01, 0xAD, 0x6E, 0x01}};
CAN_frame BMW_6F4_CELL_CONTINUE = {.FD = false,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0x07, 0x04, 0x31, 0x03, 0xAD, 0x6E}};
//The above CAN messages need to be sent towards the battery to keep it alive
uint8_t startup_counter_contactor = 0;
uint8_t alive_counter_20ms = 0;
uint8_t alive_counter_100ms = 0;
uint8_t alive_counter_200ms = 0;
uint8_t alive_counter_500ms = 0;
uint8_t alive_counter_1000ms = 0;
uint8_t alive_counter_5000ms = 0;
uint8_t BMW_1D0_counter = 0;
uint8_t BMW_13E_counter = 0;
uint8_t BMW_380_counter = 0;
uint32_t BMW_328_seconds = 243785948; // Initialized to make the battery think vehicle was made 7.7years ago
uint16_t BMW_328_days =
9244; //Time since 1.1.2000. Hacky implementation to make it think current date is 23rd April 2025
uint32_t BMS_328_seconds_to_day = 0; //Counter to keep track of days uptime
bool battery_awake = false;
bool battery_info_available = false;
bool skipCRCCheck = false;
bool CRCCheckPassedPreviously = false;
uint16_t cellvoltage_temp_mV = 0;
uint32_t battery_serial_number = 0;
uint32_t battery_available_power_shortterm_charge = 0;
uint32_t battery_available_power_shortterm_discharge = 0;
uint32_t battery_available_power_longterm_charge = 0;
uint32_t battery_available_power_longterm_discharge = 0;
uint32_t battery_BEV_available_power_shortterm_charge = 0;
uint32_t battery_BEV_available_power_shortterm_discharge = 0;
uint32_t battery_BEV_available_power_longterm_charge = 0;
uint32_t battery_BEV_available_power_longterm_discharge = 0;
uint16_t battery_energy_content_maximum_Wh = 0;
uint16_t battery_display_SOC = 0;
uint16_t battery_volts = 0;
uint16_t battery_HVBatt_SOC = 0;
uint16_t battery_DC_link_voltage = 0;
uint16_t battery_max_charge_voltage = 0;
uint16_t battery_min_discharge_voltage = 0;
uint16_t battery_predicted_energy_charge_condition = 0;
uint16_t battery_predicted_energy_charging_target = 0;
uint16_t battery_actual_value_power_heating = 0; //0 - 4094 W
uint16_t battery_prediction_voltage_shortterm_charge = 0;
uint16_t battery_prediction_voltage_shortterm_discharge = 0;
uint16_t battery_prediction_voltage_longterm_charge = 0;
uint16_t battery_prediction_voltage_longterm_discharge = 0;
uint16_t battery_prediction_duration_charging_minutes = 0;
uint16_t battery_target_voltage_in_CV_mode = 0;
uint16_t battery_soc = 0;
uint16_t battery_soc_hvmax = 0;
uint16_t battery_soc_hvmin = 0;
uint16_t battery_capacity_cah = 0;
int16_t battery_temperature_HV = 0;
int16_t battery_temperature_heat_exchanger = 0;
int16_t battery_temperature_max = 0;
int16_t battery_temperature_min = 0;
int16_t battery_max_charge_amperage = 0;
int16_t battery_max_discharge_amperage = 0;
int16_t battery_current = 0;
uint8_t battery_status_error_isolation_external_Bordnetz = 0;
uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
uint8_t battery_request_cooling = 0;
uint8_t battery_status_valve_cooling = 0;
uint8_t battery_status_error_locking = 0;
uint8_t battery_status_precharge_locked = 0;
uint8_t battery_status_disconnecting_switch = 0;
uint8_t battery_status_emergency_mode = 0;
uint8_t battery_request_service = 0;
uint8_t battery_error_emergency_mode = 0;
uint8_t battery_status_error_disconnecting_switch = 0;
uint8_t battery_status_warning_isolation = 0;
uint8_t battery_status_cold_shutoff_valve = 0;
uint8_t battery_request_open_contactors = 0;
uint8_t battery_request_open_contactors_instantly = 0;
uint8_t battery_request_open_contactors_fast = 0;
uint8_t battery_charging_condition_delta = 0;
uint8_t battery_status_service_disconnection_plug = 0;
uint8_t battery_status_measurement_isolation = 0;
uint8_t battery_request_abort_charging = 0;
uint8_t battery_prediction_time_end_of_charging_minutes = 0;
uint8_t battery_request_operating_mode = 0;
uint8_t battery_request_charging_condition_minimum = 0;
uint8_t battery_request_charging_condition_maximum = 0;
uint8_t battery_status_cooling_HV = 0; //1 works, 2 does not start
uint8_t battery_status_diagnostics_HV = 0; // 0 all OK, 1 HV protection function error, 2 diag not yet expired
uint8_t battery_status_diagnosis_powertrain_maximum_multiplexer = 0;
uint8_t battery_status_diagnosis_powertrain_immediate_multiplexer = 0;
uint8_t battery_ID2 = 0;
uint8_t battery_soh = 99;
uint8_t message_data[50];
uint8_t next_data = 0;
uint8_t current_cell_polled = 0;
};
#endif

View file

@ -6,11 +6,13 @@
#include "BMW-IX-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10 = 0; // will store last time a 20ms CAN Message was send
static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
static unsigned long previousMillis1000 = 0; // will store last time a 600ms CAN Message was send
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
@ -19,6 +21,27 @@ enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE
static CmdState cmdState = SOC;
static bool battery_awake = false;
bool contactorCloseReq = false;
struct ContactorCloseRequestStruct {
bool previous;
bool present;
} ContactorCloseRequest = {false, false};
struct ContactorStateStruct {
bool closed;
bool open;
};
ContactorStateStruct ContactorState = {false, true};
struct InverterContactorCloseRequestStruct {
bool previous;
bool present;
};
InverterContactorCloseRequestStruct InverterContactorCloseRequest = {false, false};
/*
SME output:
0x12B8D087 5000ms - Extended ID
@ -89,13 +112,17 @@ CAN_frame BMWiX_12B8D087 = {.FD = true,
.data = {0xFC, 0xFF}}; // 5000ms SME output - Static values
*/
CAN_frame BMWiX_16E = {
.FD = true,
CAN_frame BMWiX_16E = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x16E,
// .data = {TODO:, TODO:, TODO: 0xC8 or 0xC9, 0xFF, TODO:, 0xC9, TODO:, TODO:, }
}; // CCU output
.data = {0x00, // Almost any possible number in 0x00 and 0xFF
0xA0, // Almost any possible number in 0xA0 and 0xAF
0xC9, 0xFF,
0x60, // FIXME: find out what this value represents
0xC9,
0x3A, // 0x3A to close contactors, 0x33 to open contactors
0xF7}}; // 0xF7 to close contactors, 0xF0 to open contactors // CCU output.
CAN_frame BMWiX_188 = {.FD = true,
.ext_ID = false,
@ -127,12 +154,12 @@ CAN_frame BMWiX_21D = {
// .data = {TODO:, TODO:, TODO:, 0xFF, 0xFF, 0xFF, 0xFF, TODO:}
}; // FIXME:(add transmitter node) output - request heating and air conditioning system 1
CAN_frame BMWiX_276 = {
.FD = true,
CAN_frame BMWiX_276 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x276,
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC}}; // 5000ms BDC output - vehicle condition
.data = {0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
0xFD}}; // BDC output - vehicle condition. Used for contactor closing
CAN_frame BMWiX_2ED = {
.FD = true,
@ -228,8 +255,13 @@ CAN_frame BMWiX_510 = {
.ext_ID = false,
.DLC = 8,
.ID = 0x510,
.data = {0x40, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common
.data = {
0x40, 0x10,
0x04, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
0x00, 0x00,
0x80, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
0x01,
0x00}}; // 100ms BDC output - Values change in car logs, these bytes are the most common. Used for contactor closing
CAN_frame BMWiX_6D = {
.FD = true,
@ -420,8 +452,6 @@ CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCA}};
//Request Data CAN End
static bool battery_awake = false;
//Setup UDS values to poll for
CAN_frame* UDS_REQUESTS100MS[] = {&BMWiX_6F4_REQUEST_CELL_TEMP,
&BMWiX_6F4_REQUEST_SOC,
@ -495,6 +525,9 @@ const unsigned long STALE_PERIOD =
static uint8_t current_cell_polled = 0;
static uint16_t counter_10ms = 0; // max 65535 --> 655.35 seconds
static uint8_t counter_100ms = 0; // max 255 --> 25.5 seconds
// Function to check if a value has gone stale over a specified time period
bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) {
unsigned long currentTime = millis();
@ -629,13 +662,53 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
}
void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
battery_awake = true;
switch (rx_frame.ID) {
case 0x112:
case 0x12B8D087:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x1D2:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x20B:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x2E2:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x31F:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x3EA:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x453:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x486:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x49C:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4A1:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4BB:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4D0:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x507:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x587:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x607: //SME responds to UDS requests on 0x607
if (rx_frame.DLC > 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x10 &&
rx_frame.data.u8[2] == 0xE3 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5) {
//First of multi frame data - Parse the first frame
@ -785,7 +858,7 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
(rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot
#ifdef DEBUG_LOG
logging.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset");
#endif
#endif // DEBUG_LOG
//set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type
transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
} else { //Only ingest values if they are not the 10V Error state
@ -832,38 +905,94 @@ void handle_incoming_can_frame_battery(CAN_frame rx_frame) {
battery_serial_number = strtoul(numberString, NULL, 10);
}
break;
case 0x7AB:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x8F:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0xD0D087:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
default:
break;
}
}
void transmit_can_battery(unsigned long currentMillis) {
// We can always send CAN as the iX BMS will wake up on vehicle comms
if (currentMillis - previousMillis10 >= INTERVAL_10_MS) {
previousMillis10 = currentMillis;
ContactorCloseRequest.present = contactorCloseReq;
// Detect edge
if (ContactorCloseRequest.previous == false && ContactorCloseRequest.present == true) {
// Rising edge detected
#ifdef DEBUG_LOG
logging.println("Rising edge detected. Resetting 10ms counter.");
#endif // DEBUG_LOG
counter_10ms = 0; // reset counter
} else if (ContactorCloseRequest.previous == true && ContactorCloseRequest.present == false) {
// Dropping edge detected
#ifdef DEBUG_LOG
logging.println("Dropping edge detected. Resetting 10ms counter.");
#endif // DEBUG_LOG
counter_10ms = 0; // reset counter
}
ContactorCloseRequest.previous = ContactorCloseRequest.present;
HandleBmwIxCloseContactorsRequest(counter_10ms);
HandleBmwIxOpenContactorsRequest(counter_10ms);
counter_10ms++;
//if (battery_awake) { //We can always send CAN as the iX BMS will wake up on vehicle comms
// prevent counter overflow: 2^16-1 = 65535
if (counter_10ms == 65535) {
counter_10ms = 1; // set to 1, to differentiate the counter being set to 0 by the functions above
}
}
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
HandleIncomingInverterRequest();
//Loop through and send a different UDS request each cycle
//Loop through and send a different UDS request once the contactors are closed
if (contactorCloseReq == true &&
ContactorState.closed ==
true) { // Do not send unless the contactors are requested to be closed and are closed, as sending these does not allow the contactors to close
uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery);
transmit_can_frame(UDS_REQUESTS100MS[uds_req_id_counter],
can_config.battery); // FIXME: sending these does not allow the contactors to close
} else { // FIXME: hotfix: If contactors are not requested to be closed, ensure the battery is reported as alive, even if no CAN messages are received
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
}
// Keep contactors closed if needed
BmwIxKeepContactorsClosed(counter_100ms);
counter_100ms++;
if (counter_100ms == 140) {
counter_100ms = 0; // reset counter every 14 seconds
}
//Send SME Keep alive values 100ms
transmit_can_frame(&BMWiX_510, can_config.battery);
//transmit_can_frame(&BMWiX_510, can_config.battery);
}
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
previousMillis200 = currentMillis;
//Send SME Keep alive values 200ms
BMWiX_C0.data.u8[0] = increment_C0_counter(BMWiX_C0.data.u8[0]); //Keep Alive 1
transmit_can_frame(&BMWiX_C0, can_config.battery);
//BMWiX_C0.data.u8[0] = increment_C0_counter(BMWiX_C0.data.u8[0]); //Keep Alive 1
//transmit_can_frame(&BMWiX_C0, can_config.battery);
}
// Send 1000ms CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
HandleIncomingUserRequest();
}
// Send 10000ms CAN Message
if (currentMillis - previousMillis10000 >= INTERVAL_10_S) {
previousMillis10000 = currentMillis;
transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START2, can_config.battery);
transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START, can_config.battery);
//transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START2, can_config.battery);
//transmit_can_frame(&BMWiX_6F4_REQUEST_BALANCING_START, can_config.battery);
}
}
@ -872,7 +1001,7 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.system.info.battery_protocol[63] = '\0';
//Reset Battery at bootup
transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
//transmit_can_frame(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
//Before we have started up and detected which battery is in use, use 108S values
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
@ -883,4 +1012,211 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif
void HandleIncomingUserRequest(void) {
// Debug user request to open or close the contactors
#ifdef DEBUG_LOG
logging.print("User request: contactor close: ");
logging.print(datalayer_extended.bmwix.UserRequestContactorClose);
logging.print(" User request: contactor open: ");
logging.println(datalayer_extended.bmwix.UserRequestContactorOpen);
#endif // DEBUG_LOG
if ((datalayer_extended.bmwix.UserRequestContactorClose == false) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == false)) {
// do nothing
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == true) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == false)) {
BmwIxCloseContactors();
// set user request to false
datalayer_extended.bmwix.UserRequestContactorClose = false;
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == false) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == true)) {
BmwIxOpenContactors();
// set user request to false
datalayer_extended.bmwix.UserRequestContactorOpen = false;
} else if ((datalayer_extended.bmwix.UserRequestContactorClose == true) &&
(datalayer_extended.bmwix.UserRequestContactorOpen == true)) {
// these flasgs should not be true at the same time, therefore open contactors, as that is the safest state
BmwIxOpenContactors();
// set user request to false
datalayer_extended.bmwix.UserRequestContactorClose = false;
datalayer_extended.bmwix.UserRequestContactorOpen = false;
// print error, as both these flags shall not be true at the same time
#ifdef DEBUG_LOG
logging.println(
"Error: user requested contactors to close and open at the same time. Contactors have been opened.");
#endif // DEBUG_LOG
}
}
void HandleIncomingInverterRequest(void) {
InverterContactorCloseRequest.present = datalayer.system.status.inverter_allows_contactor_closing;
// Detect edge
if (InverterContactorCloseRequest.previous == false && InverterContactorCloseRequest.present == true) {
// Rising edge detected
#ifdef DEBUG_LOG
logging.println("Inverter requests to close contactors");
#endif // DEBUG_LOG
BmwIxCloseContactors();
} else if (InverterContactorCloseRequest.previous == true && InverterContactorCloseRequest.present == false) {
// Falling edge detected
#ifdef DEBUG_LOG
logging.println("Inverter requests to open contactors");
#endif // DEBUG_LOG
BmwIxOpenContactors();
} // else: do nothing
// Update state
InverterContactorCloseRequest.previous = InverterContactorCloseRequest.present;
}
void BmwIxCloseContactors(void) {
#ifdef DEBUG_LOG
logging.println("Closing contactors");
#endif // DEBUG_LOG
contactorCloseReq = true;
}
void BmwIxOpenContactors(void) {
#ifdef DEBUG_LOG
logging.println("Opening contactors");
#endif // DEBUG_LOG
contactorCloseReq = false;
counter_100ms = 0; // reset counter, such that keep contactors closed message sequence starts from the beginning
}
void HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms) {
if (contactorCloseReq == true) { // Only when contactor close request is set to true
if (ContactorState.closed == false &&
ContactorState.open ==
true) { // Only when the following commands have not been completed yet, because it shall not be run when commands have already been run, AND only when contactor open commands have finished
// Initially 0x510[2] needs to be 0x02, and 0x510[5] needs to be 0x00
BMWiX_510.data = {0x40, 0x10,
0x02, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
0x00, 0x00,
0x00, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
0x01, // 0x01 at contactor closing
0x00}; // Explicit declaration, to prevent modification by other functions
BMWiX_16E.data = {
0x00, // Almost any possible number in 0x00 and 0xFF
0xA0, // Almost any possible number in 0xA0 and 0xAF
0xC9, 0xFF, 0x60,
0xC9, 0x3A, 0xF7}; // Explicit declaration of default values, to prevent modification by other functions
if (counter_10ms == 0) {
// @0 ms
transmit_can_frame(&BMWiX_510, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 1/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 5) {
// @50 ms
transmit_can_frame(&BMWiX_276, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x276 - 2/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 10) {
// @100 ms
BMWiX_510.data.u8[2] = 0x04; // TODO: check if needed
transmit_can_frame(&BMWiX_510, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 3/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 20) {
// @200 ms
BMWiX_510.data.u8[2] = 0x10; // TODO: check if needed
BMWiX_510.data.u8[5] = 0x80; // needed to close contactors
transmit_can_frame(&BMWiX_510, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x510 - 4/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 30) {
// @300 ms
BMWiX_16E.data.u8[0] = 0x6A;
BMWiX_16E.data.u8[1] = 0xAD;
transmit_can_frame(&BMWiX_16E, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x16E - 5/6");
#endif // DEBUG_LOG
} else if (counter_10ms == 50) {
// @500 ms
BMWiX_16E.data.u8[0] = 0x03;
BMWiX_16E.data.u8[1] = 0xA9;
transmit_can_frame(&BMWiX_16E, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Transmitted 0x16E - 6/6");
#endif // DEBUG_LOG
ContactorState.closed = true;
ContactorState.open = false;
}
}
}
}
void BmwIxKeepContactorsClosed(uint8_t counter_100ms) {
if ((ContactorState.closed == true) && (ContactorState.open == false)) {
BMWiX_510.data = {0x40, 0x10,
0x04, // 0x02 at contactor closing, afterwards 0x04 and 0x10, 0x00 to open contactors
0x00, 0x00,
0x80, // 0x00 at start of contactor closing, changing to 0x80, afterwards 0x80
0x01, // 0x01 at contactor closing
0x00}; // Explicit declaration, to prevent modification by other functions
BMWiX_16E.data = {0x00, // Almost any possible number in 0x00 and 0xFF
0xA0, // Almost any possible number in 0xA0 and 0xAF
0xC9, 0xFF, 0x60,
0xC9, 0x3A, 0xF7}; // Explicit declaration, to prevent modification by other functions
if (counter_100ms == 0) {
#ifdef DEBUG_LOG
logging.println("Sending keep contactors closed messages started");
#endif // DEBUG_LOG
// @0 ms
transmit_can_frame(&BMWiX_510, can_config.battery);
} else if (counter_100ms == 7) {
// @ 730 ms
BMWiX_16E.data.u8[0] = 0x8C;
BMWiX_16E.data.u8[1] = 0xA0;
transmit_can_frame(&BMWiX_16E, can_config.battery);
} else if (counter_100ms == 24) {
// @2380 ms
transmit_can_frame(&BMWiX_510, can_config.battery);
} else if (counter_100ms == 29) {
// @ 2900 ms
BMWiX_16E.data.u8[0] = 0x02;
BMWiX_16E.data.u8[1] = 0xA7;
transmit_can_frame(&BMWiX_16E, can_config.battery);
#ifdef DEBUG_LOG
logging.println("Sending keep contactors closed messages finished");
#endif // DEBUG_LOG
} else if (counter_100ms == 140) {
// @14000 ms
// reset counter (outside of this function)
}
}
}
void HandleBmwIxOpenContactorsRequest(uint16_t counter_10ms) {
if (contactorCloseReq == false) { // if contactors are not requested to be closed, they are requested to be opened
if (ContactorState.open == false) { // only if contactors are not open yet
// message content to quickly open contactors
if (counter_10ms == 0) {
// @0 ms (0.00) RX0 510 [8] 40 10 00 00 00 80 00 00
BMWiX_510.data = {0x40, 0x10, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00}; // Explicit declaration, to prevent modification by other functions
transmit_can_frame(&BMWiX_510, can_config.battery);
// set back to default values
BMWiX_510.data = {0x40, 0x10, 0x04, 0x00, 0x00, 0x80, 0x01, 0x00}; // default values
} else if (counter_10ms == 6) {
// @60 ms (0.06) RX0 16E [8] E6 A4 C8 FF 60 C9 33 F0
BMWiX_16E.data = {0xE6, 0xA4, 0xC8, 0xFF,
0x60, 0xC9, 0x33, 0xF0}; // Explicit declaration, to prevent modification by other functions
transmit_can_frame(&BMWiX_16E, can_config.battery);
// set back to default values
BMWiX_16E.data = {0x00, 0xA0, 0xC9, 0xFF, 0x60, 0xC9, 0x3A, 0xF7}; // default values
ContactorState.closed = false;
ContactorState.open = true;
}
}
}
}
#endif // BMW_IX_BATTERY

View file

@ -19,4 +19,69 @@
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);
/**
* @brief Handle incoming user request to close or open contactors
*
* @param[in] void
*
* @return void
*/
void HandleIncomingUserRequest(void);
/**
* @brief Handle incoming inverter request to close or open contactors.alignas
*
* This function uses the "inverter_allows_contactor_closing" flag from the datalayer, to determine if CAN messages shall be sent to the battery to close or open the contactors.
*
* @param[in] void
*
* @return void
*/
void HandleIncomingInverterRequest(void);
/**
* @brief Close contactors of the BMW iX battery
*
* @param[in] void
*
* @return void
*/
void BmwIxCloseContactors(void);
/**
* @brief Handle close contactors requests for the BMW iX battery
*
* @param[in] counter_10ms Counter that increments by 1, every 10ms
*
* @return void
*/
void HandleBmwIxCloseContactorsRequest(uint16_t counter_10ms);
/**
* @brief Keep contactors of the BMW iX battery closed
*
* @param[in] counter_100ms Counter that increments by 1, every 100 ms
*
* @return void
*/
void BmwIxKeepContactorsClosed(uint8_t counter_100ms);
/**
* @brief Open contactors of the BMW iX battery
*
* @param[in] void
*
* @return void
*/
void BmwIxOpenContactors(void);
/**
* @brief Handle open contactors requests for the BMW iX battery
*
* @param[in] counter_10ms Counter that increments by 1, every 10ms
*
* @return void
*/
void HandleBmwIxOpenContactorsRequest(uint16_t counter_10ms);
#endif

View file

@ -41,6 +41,9 @@ typedef struct {
} DATALAYER_INFO_BOLTAMPERA;
typedef struct {
/** User requesting contactor open or close via WebUI*/
bool UserRequestContactorClose = false;
bool UserRequestContactorOpen = false;
/** uint16_t */
/** Terminal 30 - 12V SME Supply Voltage */
uint16_t T30_Voltage = 0;

View file

@ -58,6 +58,8 @@ String advanced_battery_processor(const String& var) {
#endif //BOLT_AMPERA_BATTERY
#ifdef BMW_IX_BATTERY
content += "<button onclick='askContactorClose()'>Close Contactors</button>";
content += "<button onclick='askContactorOpen()'>Open Contactors</button>";
content +=
"<h4>Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) +
" dV</h4>";
@ -1492,6 +1494,7 @@ String advanced_battery_processor(const String& var) {
#endif
content += "</div>";
content += "<script>";
content +=
"function askTeslaClearIsolation() { if (window.confirm('Are you sure you want to clear any active isolation "
@ -1504,6 +1507,7 @@ String advanced_battery_processor(const String& var) {
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askTeslaResetBMS() { if (window.confirm('Are you sure you want to reset the "
@ -1516,6 +1520,7 @@ String advanced_battery_processor(const String& var) {
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askResetCrash() { if (window.confirm('Are you sure you want to reset crash data? "
@ -1540,6 +1545,33 @@ String advanced_battery_processor(const String& var) {
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askContactorClose() { if (window.confirm('Are you sure you want to tirgger "
"a contactor close request?')) { "
"bmwIxCloseContactorRequest(); } }";
content += "function bmwIxCloseContactorRequest() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/bmwIxCloseContactorRequest', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askContactorOpen() { if (window.confirm('Are you sure you want to tirgger "
"a contactor open request?')) { "
"bmwIxOpenContactorRequest(); } }";
content += "function bmwIxOpenContactorRequest() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/bmwIxOpenContactorRequest', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function askResetSOH() { if (window.confirm('Are you sure you want to reset degradation data? "
@ -1552,6 +1584,7 @@ String advanced_battery_processor(const String& var) {
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
content += "<script>";
content +=
"function Volvo_askEraseDTC() { if (window.confirm('Are you sure you want to erase DTCs?')) { "
@ -1585,6 +1618,7 @@ String advanced_battery_processor(const String& var) {
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
// Additial functions added
content += "<script>";
content += "function exportLog() { window.location.href = '/export_log'; }";

View file

@ -599,6 +599,24 @@ void init_webserver() {
request->send(200, "text/plain", "Updated successfully");
});
// Route for closing BMW iX Contactors
server.on("/bmwIxCloseContactorRequest", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.bmwix.UserRequestContactorClose = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for opening BMW iX Contactors
server.on("/bmwIxOpenContactorRequest", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
return request->requestAuthentication();
}
datalayer_extended.bmwix.UserRequestContactorOpen = true;
request->send(200, "text/plain", "Updated successfully");
});
// Route for resetting SOH on Nissan LEAF batteries
server.on("/resetSOH", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
@ -1207,15 +1225,14 @@ String processor(const String& var) {
}
}
content += "<h4>Automatic contactor closing allowed:</h4>";
content += "<h4>Battery: ";
content += "<h4>Battery allows contactor closing: ";
if (datalayer.system.status.battery_allows_contactor_closing == true) {
content += "<span>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Inverter: ";
content += " Inverter allows contactor closing: ";
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
content += "<span>&#10003;</span></h4>";
} else {

View file

@ -1,143 +1,11 @@
#include "../include.h"
#ifdef FERROAMP_CAN
#include "../communication/can/comm_can.h"
#include "../datalayer/datalayer.h"
#include "FERROAMP-CAN.h"
//#define SEND_0 //If defined, the messages will have ID ending with 0 (useful for some inverters)
#define SEND_1 //If defined, the messages will have ID ending with 1 (useful for some inverters)
#define INVERT_LOW_HIGH_BYTES //If defined, certain frames will have inverted low/high bytes \
//useful for some inverters like Sofar that report the voltages incorrect otherwise
#define SET_30K_OFFSET //If defined, current values are sent with a 30k offest (useful for ferroamp)
/* Some inverters need to see a specific amount of cells/modules to emulate a specific Pylon battery.
Change the following only if your inverter is generating fault codes about voltage range */
#define TOTAL_CELL_AMOUNT 120 //Adjust this parameter in steps of 120 to add another 14,2kWh of capacity
#define MODULES_IN_SERIES 4
#define CELLS_PER_MODULE 30
#define VOLTAGE_LEVEL 384
#define AH_CAPACITY 37
/* Do not change code below unless you are sure what you are doing */
//Actual content messages
CAN_frame PYLON_7310 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7310,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame PYLON_7311 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7311,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame PYLON_7320 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7320,
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame PYLON_7321 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7321,
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame PYLON_4210 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4210,
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
CAN_frame PYLON_4220 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4220,
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
CAN_frame PYLON_4230 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4230,
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
CAN_frame PYLON_4240 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4240,
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
CAN_frame PYLON_4250 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4250,
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4260 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4260,
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
CAN_frame PYLON_4270 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4270,
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
CAN_frame PYLON_4280 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4280,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4290 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4290,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4211 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4211,
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
CAN_frame PYLON_4221 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4221,
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
CAN_frame PYLON_4231 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4231,
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
CAN_frame PYLON_4241 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4241,
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
CAN_frame PYLON_4251 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4251,
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4261 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4261,
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
CAN_frame PYLON_4271 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4271,
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
CAN_frame PYLON_4281 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4281,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4291 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4291,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static uint16_t cell_tweaked_max_voltage_mV = 3300;
static uint16_t cell_tweaked_min_voltage_mV = 3300;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
void FerroampCanInverter::
update_values() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//There are more mappings that could be added, but this should be enough to use as a starting point
// Note we map both 0 and 1 messages
@ -437,7 +305,7 @@ void update_values_can_inverter() { //This function maps all the values fetched
}
}
void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
void FerroampCanInverter::map_can_frame_to_variable(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x4200: //Message originating from inverter. Depending on which data is required, act accordingly
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -453,11 +321,11 @@ void map_can_frame_to_variable_inverter(CAN_frame rx_frame) {
}
}
void transmit_can_inverter(unsigned long currentMillis) {
void FerroampCanInverter::transmit_can(unsigned long currentMillis) {
// No periodic sending, we only react on received can messages
}
void send_setup_info() { //Ensemble information
void FerroampCanInverter::send_setup_info() { //Ensemble information
#ifdef SEND_0
transmit_can_frame(&PYLON_7310, can_config.inverter);
transmit_can_frame(&PYLON_7320, can_config.inverter);
@ -468,7 +336,7 @@ void send_setup_info() { //Ensemble information
#endif
}
void send_system_data() { //System equipment information
void FerroampCanInverter::send_system_data() { //System equipment information
#ifdef SEND_0
transmit_can_frame(&PYLON_4210, can_config.inverter);
transmit_can_frame(&PYLON_4220, can_config.inverter);
@ -492,7 +360,8 @@ void send_system_data() { //System equipment information
transmit_can_frame(&PYLON_4291, can_config.inverter);
#endif
}
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
void FerroampCanInverter::setup(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Ferroamp Pylon battery over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}

View file

@ -3,10 +3,154 @@
#include "../include.h"
#define CAN_INVERTER_SELECTED
#define SELECTED_INVERTER_CLASS FerroampCanInverter
class FerroampCanInverter : public CanInverterProtocol {
public:
void setup();
void update_values();
void transmit_can(unsigned long currentMillis);
void map_can_frame_to_variable(CAN_frame rx_frame);
private:
void send_system_data();
void send_setup_info();
void transmit_can_frame(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
//#define SEND_0 //If defined, the messages will have ID ending with 0 (useful for some inverters)
#define SEND_1 //If defined, the messages will have ID ending with 1 (useful for some inverters)
#define INVERT_LOW_HIGH_BYTES //If defined, certain frames will have inverted low/high bytes \
//useful for some inverters like Sofar that report the voltages incorrect otherwise
#define SET_30K_OFFSET //If defined, current values are sent with a 30k offest (useful for ferroamp)
/* Some inverters need to see a specific amount of cells/modules to emulate a specific Pylon battery.
Change the following only if your inverter is generating fault codes about voltage range */
#define TOTAL_CELL_AMOUNT 120 //Adjust this parameter in steps of 120 to add another 14,2kWh of capacity
#define MODULES_IN_SERIES 4
#define CELLS_PER_MODULE 30
#define VOLTAGE_LEVEL 384
#define AH_CAPACITY 37
//Actual content messages
CAN_frame PYLON_7310 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7310,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame PYLON_7311 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7311,
.data = {0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x00, 0x00}};
CAN_frame PYLON_7320 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7320,
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame PYLON_7321 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x7321,
.data = {TOTAL_CELL_AMOUNT, (uint8_t)(TOTAL_CELL_AMOUNT >> 8), MODULES_IN_SERIES,
CELLS_PER_MODULE, (uint8_t)(VOLTAGE_LEVEL & 0x00FF), (uint8_t)(VOLTAGE_LEVEL >> 8),
AH_CAPACITY, (uint8_t)(AH_CAPACITY >> 8)}};
CAN_frame PYLON_4210 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4210,
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
CAN_frame PYLON_4220 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4220,
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
CAN_frame PYLON_4230 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4230,
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
CAN_frame PYLON_4240 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4240,
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
CAN_frame PYLON_4250 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4250,
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4260 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4260,
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
CAN_frame PYLON_4270 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4270,
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
CAN_frame PYLON_4280 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4280,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4290 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4290,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4211 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4211,
.data = {0xA5, 0x09, 0x30, 0x75, 0x9D, 0x04, 0x2E, 0x64}};
CAN_frame PYLON_4221 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4221,
.data = {0x8C, 0x0A, 0xE9, 0x07, 0x4A, 0x79, 0x4A, 0x79}};
CAN_frame PYLON_4231 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4231,
.data = {0xDF, 0x0C, 0xDA, 0x0C, 0x03, 0x00, 0x06, 0x00}};
CAN_frame PYLON_4241 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4241,
.data = {0x7E, 0x04, 0x62, 0x04, 0x11, 0x00, 0x03, 0x00}};
CAN_frame PYLON_4251 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4251,
.data = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4261 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4261,
.data = {0xAC, 0xC7, 0x74, 0x27, 0x03, 0x00, 0x02, 0x00}};
CAN_frame PYLON_4271 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4271,
.data = {0x7E, 0x04, 0x62, 0x04, 0x05, 0x00, 0x01, 0x00}};
CAN_frame PYLON_4281 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4281,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame PYLON_4291 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x4291,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
uint16_t cell_tweaked_max_voltage_mV = 3300;
uint16_t cell_tweaked_min_voltage_mV = 3300;
};
#endif