mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
Minimum viable product for double battery
This commit is contained in:
parent
4cdbc50aaf
commit
f83bbc35eb
10 changed files with 521 additions and 33 deletions
|
@ -216,6 +216,9 @@ void core_loop(void* task_time_us) {
|
||||||
led_exe();
|
led_exe();
|
||||||
#ifdef CONTACTOR_CONTROL
|
#ifdef CONTACTOR_CONTROL
|
||||||
handle_contactors(); // Take care of startup precharge/contactor closing
|
handle_contactors(); // Take care of startup precharge/contactor closing
|
||||||
|
#endif
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
handle_CAN_contactors();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us);
|
END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us);
|
||||||
|
@ -590,6 +593,9 @@ void receive_can2() { // This function is similar to receive_can, but just take
|
||||||
if (rx_frame2.FIR.B.FF == CAN_frame_std) { // New standard frame
|
if (rx_frame2.FIR.B.FF == CAN_frame_std) { // New standard frame
|
||||||
#ifdef BYD_CAN
|
#ifdef BYD_CAN
|
||||||
receive_can_byd(rx_frame2);
|
receive_can_byd(rx_frame2);
|
||||||
|
#endif
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
receive_can_battery2(rx_frame2);
|
||||||
#endif
|
#endif
|
||||||
} else { // New extended frame
|
} else { // New extended frame
|
||||||
#ifdef PYLON_CAN
|
#ifdef PYLON_CAN
|
||||||
|
@ -611,6 +617,14 @@ void send_can2() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
void handle_CAN_contactors() {
|
||||||
|
if (abs(datalayer.battery.status.voltage_dV - datalayer.battery2.status.voltage_dV) < 50) {
|
||||||
|
datalayer.system.status.battery2_allows_contactor_closing = true;
|
||||||
|
} //TODO: Shall we handle opening incase of fault here?
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef CONTACTOR_CONTROL
|
#ifdef CONTACTOR_CONTROL
|
||||||
void handle_contactors() {
|
void handle_contactors() {
|
||||||
// First check if we have any active errors, incase we do, turn off the battery
|
// First check if we have any active errors, incase we do, turn off the battery
|
||||||
|
@ -733,9 +747,17 @@ void update_SOC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void summarize_battery_values() {
|
||||||
|
// TODO: What needs to be summed?
|
||||||
|
}
|
||||||
|
|
||||||
void update_values() {
|
void update_values() {
|
||||||
// Battery
|
// Battery
|
||||||
update_values_battery(); // Map the fake values to the correct registers
|
update_values_battery();
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
update_values_battery2();
|
||||||
|
summarize_battery_values();
|
||||||
|
#endif
|
||||||
// Inverter
|
// Inverter
|
||||||
#ifdef BYD_CAN
|
#ifdef BYD_CAN
|
||||||
update_values_can_byd();
|
update_values_can_byd();
|
||||||
|
|
|
@ -14,9 +14,9 @@ volatile float CHARGER_END_A = 1.0; // Current at which charging is consid
|
||||||
#ifdef WEBSERVER
|
#ifdef WEBSERVER
|
||||||
volatile uint8_t AccessPointEnabled =
|
volatile uint8_t AccessPointEnabled =
|
||||||
true; //Set to either true or false incase you want the board to enable a direct wifi access point
|
true; //Set to either true or false incase you want the board to enable a direct wifi access point
|
||||||
const char* ssid = "REPLACE_WITH_YOUR_SSID"; // Maximum of 63 characters;
|
const char* ssid = "ZTE_5G_VACCINE"; // Maximum of 63 characters;
|
||||||
const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // Minimum of 8 characters;
|
const char* password = "secretpassword"; // Minimum of 8 characters;
|
||||||
const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters;
|
const char* ssidAP = "Battery Emulator"; // Maximum of 63 characters;
|
||||||
const char* passwordAP = "123456789"; // Minimum of 8 characters; set to NULL if you want the access point to be open
|
const char* passwordAP = "123456789"; // Minimum of 8 characters; set to NULL if you want the access point to be open
|
||||||
const uint8_t wifi_channel = 0; // set to 0 for automatic channel selection
|
const uint8_t wifi_channel = 0; // set to 0 for automatic channel selection
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
/* To edit battery specific limits, see also the USER_SETTINGS.cpp file*/
|
/* To edit battery specific limits, see also the USER_SETTINGS.cpp file*/
|
||||||
|
|
||||||
/* Select battery used */
|
/* Select battery used */
|
||||||
//#define BMW_I3_BATTERY
|
#define BMW_I3_BATTERY
|
||||||
//#define CHADEMO_BATTERY
|
//#define CHADEMO_BATTERY
|
||||||
//#define IMIEV_CZERO_ION_BATTERY
|
//#define IMIEV_CZERO_ION_BATTERY
|
||||||
//#define KIA_HYUNDAI_64_BATTERY
|
//#define KIA_HYUNDAI_64_BATTERY
|
||||||
|
@ -20,10 +20,11 @@
|
||||||
//#define TESLA_MODEL_3_BATTERY
|
//#define TESLA_MODEL_3_BATTERY
|
||||||
//#define VOLVO_SPA_BATTERY
|
//#define VOLVO_SPA_BATTERY
|
||||||
//#define TEST_FAKE_BATTERY
|
//#define TEST_FAKE_BATTERY
|
||||||
|
#define DOUBLE_BATTERY //Enable this line if you use two identical batteries at the same time (requires DUAL_CAN setup)
|
||||||
|
|
||||||
/* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */
|
/* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */
|
||||||
//#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus
|
//#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus
|
||||||
//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
|
#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
|
||||||
//#define LUNA2000_MODBUS //Enable this line to emulate a "Luna2000 battery" over Modbus RTU
|
//#define LUNA2000_MODBUS //Enable this line to emulate a "Luna2000 battery" over Modbus RTU
|
||||||
//#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus
|
//#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus
|
||||||
//#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus
|
//#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus
|
||||||
|
@ -36,7 +37,7 @@
|
||||||
//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting
|
//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting
|
||||||
//#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence
|
//#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence
|
||||||
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM logic for contactors, which lower power consumption and heat generation
|
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM logic for contactors, which lower power consumption and heat generation
|
||||||
//#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for FoxESS inverters)
|
#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for some inverters / double battery)
|
||||||
//#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2517FD controller (Needed for some batteries)
|
//#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2517FD controller (Needed for some batteries)
|
||||||
//#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter)
|
//#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter)
|
||||||
//#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery)
|
//#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery)
|
||||||
|
|
|
@ -69,4 +69,9 @@ void update_values_battery();
|
||||||
void send_can_battery();
|
void send_can_battery();
|
||||||
void setup_battery(void);
|
void setup_battery(void);
|
||||||
|
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
void update_values_battery2();
|
||||||
|
void receive_can_battery2(CAN_frame_t rx_frame);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -7,24 +7,26 @@
|
||||||
#include "BMW-I3-BATTERY.h"
|
#include "BMW-I3-BATTERY.h"
|
||||||
|
|
||||||
/* Do not change code below unless you are sure what you are doing */
|
/* 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 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 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 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 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 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 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 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
|
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
|
||||||
static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive
|
static uint8_t CANstillAlive = 12; // counter for checking if CAN is still alive
|
||||||
static uint16_t CANerror = 0; // counter on how many CAN errors encountered
|
static unsigned long previousMillis20_2 = 0; // will store last time a 20ms CAN Message was send
|
||||||
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
|
static unsigned long previousMillis100_2 = 0; // will store last time a 100ms CAN Message was send
|
||||||
|
static unsigned long previousMillis200_2 = 0; // will store last time a 200ms CAN Message was send
|
||||||
static const uint16_t WUPonDuration = 477; // in milliseconds how long WUP should be ON after poweron
|
static unsigned long previousMillis500_2 = 0; // will store last time a 500ms CAN Message was send
|
||||||
static const uint16_t WUPoffDuration = 105; // in milliseconds how long WUP should be OFF after on pulse
|
static unsigned long previousMillis640_2 = 0; // will store last time a 600ms CAN Message was send
|
||||||
unsigned long lastChangeTime; // Variables to store timestamps
|
static unsigned long previousMillis1000_2 = 0; // will store last time a 1000ms CAN Message was send
|
||||||
unsigned long turnOnTime; // Variables to store timestamps
|
static unsigned long previousMillis5000_2 = 0; // will store last time a 5000ms CAN Message was send
|
||||||
enum State { POWERON, STATE_ON, STATE_OFF };
|
static unsigned long previousMillis10000_2 = 0; // will store last time a 10000ms CAN Message was send
|
||||||
static State WUPState = POWERON;
|
static uint8_t CAN2stillAlive = 12; // counter for checking if CAN2 is still alive
|
||||||
|
static uint16_t CANerror = 0; // counter on how many CAN errors encountered
|
||||||
|
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
|
||||||
|
|
||||||
enum CmdState { SOH, CELL_VOLTAGE, SOC, CELL_VOLTAGE_AVG };
|
enum CmdState { SOH, CELL_VOLTAGE, SOC, CELL_VOLTAGE_AVG };
|
||||||
static CmdState cmdState = SOH;
|
static CmdState cmdState = SOH;
|
||||||
|
@ -323,6 +325,7 @@ static uint8_t BMW_13E_counter = 0;
|
||||||
static uint8_t BMW_380_counter = 0;
|
static uint8_t BMW_380_counter = 0;
|
||||||
static uint32_t BMW_328_counter = 0;
|
static uint32_t BMW_328_counter = 0;
|
||||||
static bool battery_awake = false;
|
static bool battery_awake = false;
|
||||||
|
static bool battery2_awake = false;
|
||||||
|
|
||||||
static uint32_t battery_serial_number = 0;
|
static uint32_t battery_serial_number = 0;
|
||||||
static uint32_t battery_available_power_shortterm_charge = 0;
|
static uint32_t battery_available_power_shortterm_charge = 0;
|
||||||
|
@ -353,7 +356,6 @@ static uint16_t battery_soc = 0;
|
||||||
static uint16_t battery_soc_hvmax = 0;
|
static uint16_t battery_soc_hvmax = 0;
|
||||||
static uint16_t battery_soc_hvmin = 0;
|
static uint16_t battery_soc_hvmin = 0;
|
||||||
static uint16_t battery_capacity_cah = 0;
|
static uint16_t battery_capacity_cah = 0;
|
||||||
|
|
||||||
static int16_t battery_temperature_HV = 0;
|
static int16_t battery_temperature_HV = 0;
|
||||||
static int16_t battery_temperature_heat_exchanger = 0;
|
static int16_t battery_temperature_heat_exchanger = 0;
|
||||||
static int16_t battery_temperature_max = 0;
|
static int16_t battery_temperature_max = 0;
|
||||||
|
@ -394,6 +396,75 @@ static uint8_t battery_ID2 = 0;
|
||||||
static uint8_t battery_cellvoltage_mux = 0;
|
static uint8_t battery_cellvoltage_mux = 0;
|
||||||
static uint8_t battery_soh = 0;
|
static uint8_t battery_soh = 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_kWh = 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_power = 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_cellvoltage_mux = 0;
|
||||||
|
static uint8_t battery2_soh = 0;
|
||||||
|
|
||||||
static uint8_t message_data[50];
|
static uint8_t message_data[50];
|
||||||
static uint8_t next_data = 0;
|
static uint8_t next_data = 0;
|
||||||
|
|
||||||
|
@ -413,7 +484,66 @@ static uint8_t increment_alive_counter(uint8_t counter) {
|
||||||
return counter;
|
return counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
void CAN_WriteFrame(CAN_frame_t* tx_frame) {
|
||||||
|
CANMessage MCP2515Frame; //Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2
|
||||||
|
MCP2515Frame.id = tx_frame->MsgID;
|
||||||
|
//MCP2515Frame.ext = tx_frame->FIR.B.FF;
|
||||||
|
MCP2515Frame.len = tx_frame->FIR.B.DLC;
|
||||||
|
for (uint8_t i = 0; i < MCP2515Frame.len; i++) {
|
||||||
|
MCP2515Frame.data[i] = tx_frame->data.u8[i];
|
||||||
|
}
|
||||||
|
can.tryToSend(MCP2515Frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_values_battery2() { //This function maps all the values fetched via CAN2 to the battery2 datalayer
|
||||||
|
|
||||||
|
datalayer.battery2.status.real_soc = (battery2_HVBatt_SOC * 10);
|
||||||
|
|
||||||
|
datalayer.battery2.status.voltage_dV = battery2_volts; //Unit V+1 (5000 = 500.0V)
|
||||||
|
|
||||||
|
datalayer.battery2.status.current_dA = battery2_current;
|
||||||
|
|
||||||
|
datalayer.battery2.status.remaining_capacity_Wh = (battery2_energy_content_maximum_kWh * 1000); // Convert kWh to Wh
|
||||||
|
|
||||||
|
datalayer.battery2.status.soh_pptt = battery2_soh * 100;
|
||||||
|
|
||||||
|
if (battery2_BEV_available_power_longterm_discharge > 65000) {
|
||||||
|
datalayer.battery2.status.max_discharge_power_W = 65000;
|
||||||
|
} else {
|
||||||
|
datalayer.battery2.status.max_discharge_power_W = battery2_BEV_available_power_longterm_discharge;
|
||||||
|
}
|
||||||
|
if (battery2_BEV_available_power_longterm_charge > 65000) {
|
||||||
|
datalayer.battery2.status.max_charge_power_W = 65000;
|
||||||
|
} else {
|
||||||
|
datalayer.battery2.status.max_charge_power_W = battery2_BEV_available_power_longterm_charge;
|
||||||
|
}
|
||||||
|
|
||||||
|
battery2_power = (datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
|
||||||
|
|
||||||
|
datalayer.battery2.status.active_power_W = battery2_power;
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
datalayer.battery2.status.cell_min_voltage_mV = datalayer.battery2.status.cell_voltages_mV[0];
|
||||||
|
datalayer.battery2.status.cell_max_voltage_mV = datalayer.battery2.status.cell_voltages_mV[1];
|
||||||
|
|
||||||
|
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||||
|
if (!CAN2stillAlive) {
|
||||||
|
set_event(EVENT_CAN2_RX_FAILURE, 2);
|
||||||
|
datalayer.battery2.status.bms_status = FAULT; //TODO: Refactor handling of event for battery2
|
||||||
|
} else {
|
||||||
|
CAN2stillAlive--;
|
||||||
|
clear_event(EVENT_CAN2_RX_FAILURE);
|
||||||
|
}
|
||||||
|
// Check if we have encountered any malformed CAN messages
|
||||||
|
if (CANerror > MAX_CAN_FAILURES) {
|
||||||
|
set_event(EVENT_CAN_RX_WARNING, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
|
||||||
|
|
||||||
datalayer.battery.status.real_soc = (battery_HVBatt_SOC * 10);
|
datalayer.battery.status.real_soc = (battery_HVBatt_SOC * 10);
|
||||||
|
|
||||||
|
@ -492,6 +622,7 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
||||||
CANstillAlive = 12; //This message is only sent if 30C (Wakeup pin on battery) is energized with 12V
|
CANstillAlive = 12; //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_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
|
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
|
||||||
battery_HVBatt_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8 | rx_frame.data.u8[4]);
|
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 = (rx_frame.data.u8[5] & 0xC0) >> 6;
|
||||||
battery_request_open_contactors_instantly = (rx_frame.data.u8[6] & 0x03);
|
battery_request_open_contactors_instantly = (rx_frame.data.u8[6] & 0x03);
|
||||||
|
@ -650,6 +781,172 @@ void receive_can_battery(CAN_frame_t rx_frame) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void receive_can_battery2(CAN_frame_t rx_frame) {
|
||||||
|
switch (rx_frame.MsgID) {
|
||||||
|
case 0x112: //BMS [10ms] Status Of High-Voltage Battery - 2
|
||||||
|
battery2_awake = true;
|
||||||
|
CAN2stillAlive = 12; //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
|
||||||
|
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_max = (rx_frame.data.u8[6] - 50);
|
||||||
|
battery2_temperature_min = (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 (calculateCRC(rx_frame, rx_frame.FIR.B.DLC, 0x15) != rx_frame.data.u8[0]) {
|
||||||
|
//If calculated CRC does not match transmitted CRC, increase CANerror counter
|
||||||
|
CANerror++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
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 0x426: // TODO: Figure out how to trigger sending of this. Does the SME require some CAN command?
|
||||||
|
battery2_cellvoltage_mux = rx_frame.data.u8[0];
|
||||||
|
if (battery2_cellvoltage_mux == 0) {
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[0] = ((rx_frame.data.u8[1] * 10) + 1800);
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[1] = ((rx_frame.data.u8[2] * 10) + 1800);
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[2] = ((rx_frame.data.u8[3] * 10) + 1800);
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[3] = ((rx_frame.data.u8[4] * 10) + 1800);
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[4] = ((rx_frame.data.u8[5] * 10) + 1800);
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[5] = ((rx_frame.data.u8[6] * 10) + 1800);
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[5] = ((rx_frame.data.u8[7] * 10) + 1800);
|
||||||
|
}
|
||||||
|
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_kWh = (((rx_frame.data.u8[6] & 0x0F) << 8 | rx_frame.data.u8[5])) / 50;
|
||||||
|
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] / 2);
|
||||||
|
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 (rx_frame.FIR.B.DLC > 6 && next_data == 0 && rx_frame.data.u8[0] == 0xf1) {
|
||||||
|
uint8_t count2 = 6;
|
||||||
|
while (count2 < rx_frame.FIR.B.DLC && next_data < 49) {
|
||||||
|
message_data[next_data++] = rx_frame.data.u8[count2++];
|
||||||
|
}
|
||||||
|
//ESP32Can.CANWriteFrame(&BMW_6F1_CONTINUE); // tell battery to send additional messages TODO: Make this send to Can2 instead of CAN1
|
||||||
|
|
||||||
|
} else if (rx_frame.FIR.B.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.FIR.B.DLC && next_data < 49) {
|
||||||
|
message_data[next_data++] = rx_frame.data.u8[count2++];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cmdState) {
|
||||||
|
case CELL_VOLTAGE:
|
||||||
|
if (next_data >= 4) {
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[0] = (message_data[0] << 8 | message_data[1]);
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[2] = (message_data[2] << 8 | message_data[3]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CELL_VOLTAGE_AVG:
|
||||||
|
if (next_data >= 30) {
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[1] = (message_data[10] << 8 | message_data[11]) / 10;
|
||||||
|
battery2_capacity_cah = (message_data[4] << 8 | message_data[5]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SOH:
|
||||||
|
if (next_data >= 4) {
|
||||||
|
battery2_soh = message_data[3];
|
||||||
|
}
|
||||||
|
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 send_can_battery() {
|
void send_can_battery() {
|
||||||
unsigned long currentMillis = millis();
|
unsigned long currentMillis = millis();
|
||||||
|
|
||||||
|
@ -668,10 +965,6 @@ void send_can_battery() {
|
||||||
BMW_10B.data.u8[1] = 0x10; // Close contactors
|
BMW_10B.data.u8[1] = 0x10; // Close contactors
|
||||||
}
|
}
|
||||||
|
|
||||||
if (datalayer.battery.status.bms_status == FAULT) {
|
|
||||||
BMW_10B.data.u8[1] = 0x00; // Open contactors (TODO: test if this works)
|
|
||||||
}
|
|
||||||
|
|
||||||
BMW_10B.data.u8[1] = ((BMW_10B.data.u8[1] & 0xF0) + alive_counter_20ms);
|
BMW_10B.data.u8[1] = ((BMW_10B.data.u8[1] & 0xF0) + alive_counter_20ms);
|
||||||
BMW_10B.data.u8[0] = calculateCRC(BMW_10B, 3, 0x3F);
|
BMW_10B.data.u8[0] = calculateCRC(BMW_10B, 3, 0x3F);
|
||||||
|
|
||||||
|
@ -680,7 +973,17 @@ void send_can_battery() {
|
||||||
BMW_13E_counter++;
|
BMW_13E_counter++;
|
||||||
BMW_13E.data.u8[4] = BMW_13E_counter;
|
BMW_13E.data.u8[4] = BMW_13E_counter;
|
||||||
|
|
||||||
ESP32Can.CANWriteFrame(&BMW_10B);
|
if (datalayer.battery.status.bms_status == FAULT) {
|
||||||
|
} //If battery is not in Fault mode, allow contactor to close by sending 10B
|
||||||
|
else {
|
||||||
|
ESP32Can.CANWriteFrame(&BMW_10B);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DOUBLE_BATTERY //If second battery is allowed to join in, also send 10B
|
||||||
|
if (datalayer.system.status.battery2_allows_contactor_closing == true) {
|
||||||
|
CAN_WriteFrame(&BMW_10B);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
// Send 100ms CAN Message
|
// Send 100ms CAN Message
|
||||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||||
|
@ -692,6 +995,9 @@ void send_can_battery() {
|
||||||
alive_counter_100ms = increment_alive_counter(alive_counter_100ms);
|
alive_counter_100ms = increment_alive_counter(alive_counter_100ms);
|
||||||
|
|
||||||
ESP32Can.CANWriteFrame(&BMW_12F);
|
ESP32Can.CANWriteFrame(&BMW_12F);
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
CAN_WriteFrame(&BMW_12F);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
// Send 200ms CAN Message
|
// Send 200ms CAN Message
|
||||||
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
|
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
|
||||||
|
@ -703,6 +1009,9 @@ void send_can_battery() {
|
||||||
alive_counter_200ms = increment_alive_counter(alive_counter_200ms);
|
alive_counter_200ms = increment_alive_counter(alive_counter_200ms);
|
||||||
|
|
||||||
ESP32Can.CANWriteFrame(&BMW_19B);
|
ESP32Can.CANWriteFrame(&BMW_19B);
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
CAN_WriteFrame(&BMW_19B);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
// Send 500ms CAN Message
|
// Send 500ms CAN Message
|
||||||
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
|
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
|
||||||
|
@ -714,6 +1023,9 @@ void send_can_battery() {
|
||||||
alive_counter_500ms = increment_alive_counter(alive_counter_500ms);
|
alive_counter_500ms = increment_alive_counter(alive_counter_500ms);
|
||||||
|
|
||||||
ESP32Can.CANWriteFrame(&BMW_30B);
|
ESP32Can.CANWriteFrame(&BMW_30B);
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
CAN_WriteFrame(&BMW_30B);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
// Send 640ms CAN Message
|
// Send 640ms CAN Message
|
||||||
if (currentMillis - previousMillis640 >= INTERVAL_640_MS) {
|
if (currentMillis - previousMillis640 >= INTERVAL_640_MS) {
|
||||||
|
@ -721,6 +1033,10 @@ void send_can_battery() {
|
||||||
|
|
||||||
ESP32Can.CANWriteFrame(&BMW_512); // Keep BMS alive
|
ESP32Can.CANWriteFrame(&BMW_512); // Keep BMS alive
|
||||||
ESP32Can.CANWriteFrame(&BMW_5F8);
|
ESP32Can.CANWriteFrame(&BMW_5F8);
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
CAN_WriteFrame(&BMW_512);
|
||||||
|
CAN_WriteFrame(&BMW_5F8);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
// Send 1000ms CAN Message
|
// Send 1000ms CAN Message
|
||||||
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
||||||
|
@ -762,6 +1078,24 @@ void send_can_battery() {
|
||||||
ESP32Can.CANWriteFrame(&BMW_192);
|
ESP32Can.CANWriteFrame(&BMW_192);
|
||||||
ESP32Can.CANWriteFrame(&BMW_13E);
|
ESP32Can.CANWriteFrame(&BMW_13E);
|
||||||
ESP32Can.CANWriteFrame(&BMW_433);
|
ESP32Can.CANWriteFrame(&BMW_433);
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
CAN_WriteFrame(&BMW_3E8);
|
||||||
|
CAN_WriteFrame(&BMW_328);
|
||||||
|
CAN_WriteFrame(&BMW_3F9);
|
||||||
|
CAN_WriteFrame(&BMW_2E2);
|
||||||
|
CAN_WriteFrame(&BMW_41D);
|
||||||
|
CAN_WriteFrame(&BMW_3D0);
|
||||||
|
CAN_WriteFrame(&BMW_3CA);
|
||||||
|
CAN_WriteFrame(&BMW_3A7);
|
||||||
|
CAN_WriteFrame(&BMW_2CA);
|
||||||
|
CAN_WriteFrame(&BMW_3FB);
|
||||||
|
CAN_WriteFrame(&BMW_418);
|
||||||
|
CAN_WriteFrame(&BMW_1D0);
|
||||||
|
CAN_WriteFrame(&BMW_3EC);
|
||||||
|
CAN_WriteFrame(&BMW_192);
|
||||||
|
CAN_WriteFrame(&BMW_13E);
|
||||||
|
CAN_WriteFrame(&BMW_433);
|
||||||
|
#endif
|
||||||
|
|
||||||
BMW_433.data.u8[1] = 0x01; // First 433 message byte1 we send is unique, once we sent initial value send this
|
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
|
BMW_3E8.data.u8[0] = 0xF1; // First 3E8 message byte0 we send is unique, once we sent initial value send this
|
||||||
|
@ -778,11 +1112,21 @@ void send_can_battery() {
|
||||||
ESP32Can.CANWriteFrame(&BMW_3A0);
|
ESP32Can.CANWriteFrame(&BMW_3A0);
|
||||||
ESP32Can.CANWriteFrame(&BMW_592_0);
|
ESP32Can.CANWriteFrame(&BMW_592_0);
|
||||||
ESP32Can.CANWriteFrame(&BMW_592_1);
|
ESP32Can.CANWriteFrame(&BMW_592_1);
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
CAN_WriteFrame(&BMW_3FC);
|
||||||
|
CAN_WriteFrame(&BMW_3C5);
|
||||||
|
CAN_WriteFrame(&BMW_3A0);
|
||||||
|
CAN_WriteFrame(&BMW_592_0);
|
||||||
|
CAN_WriteFrame(&BMW_592_1);
|
||||||
|
#endif
|
||||||
|
|
||||||
alive_counter_5000ms = increment_alive_counter(alive_counter_5000ms);
|
alive_counter_5000ms = increment_alive_counter(alive_counter_5000ms);
|
||||||
|
|
||||||
if (BMW_380_counter < 3) {
|
if (BMW_380_counter < 3) {
|
||||||
ESP32Can.CANWriteFrame(&BMW_380); // This message stops after 3 times on startup
|
ESP32Can.CANWriteFrame(&BMW_380); // This message stops after 3 times on startup
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
CAN_WriteFrame(&BMW_380);
|
||||||
|
#endif
|
||||||
BMW_380_counter++;
|
BMW_380_counter++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -793,9 +1137,17 @@ void send_can_battery() {
|
||||||
ESP32Can.CANWriteFrame(&BMW_3E5); //Order comes from CAN logs
|
ESP32Can.CANWriteFrame(&BMW_3E5); //Order comes from CAN logs
|
||||||
ESP32Can.CANWriteFrame(&BMW_3E4);
|
ESP32Can.CANWriteFrame(&BMW_3E4);
|
||||||
ESP32Can.CANWriteFrame(&BMW_37B);
|
ESP32Can.CANWriteFrame(&BMW_37B);
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
CAN_WriteFrame(&BMW_3E5);
|
||||||
|
CAN_WriteFrame(&BMW_3E4);
|
||||||
|
CAN_WriteFrame(&BMW_37B);
|
||||||
|
#endif
|
||||||
|
|
||||||
next_data = 0;
|
next_data = 0;
|
||||||
ESP32Can.CANWriteFrame(&BMW_6F1_CELL);
|
ESP32Can.CANWriteFrame(&BMW_6F1_CELL);
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
CAN_WriteFrame(&BMW_6F1_CELL);
|
||||||
|
#endif
|
||||||
|
|
||||||
BMW_3E5.data.u8[0] = 0xFD; // First 3E5 message byte0 we send is unique, once we sent initial value send this
|
BMW_3E5.data.u8[0] = 0xFD; // First 3E5 message byte0 we send is unique, once we sent initial value send this
|
||||||
}
|
}
|
||||||
|
@ -811,6 +1163,16 @@ void setup_battery(void) { // Performs one time setup at startup
|
||||||
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
|
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
|
||||||
datalayer.battery.info.min_design_voltage_dV = 2800; // 280.0V under this, discharging further is disabled
|
datalayer.battery.info.min_design_voltage_dV = 2800; // 280.0V under this, discharging further is disabled
|
||||||
|
|
||||||
|
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||||
|
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
Serial.println("Another BMW i3 battery also selected!");
|
||||||
|
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.status.voltage_dV =
|
||||||
|
0; //Init voltage to 0 to allow contactor check to operate without fear of default values colliding
|
||||||
|
#endif
|
||||||
|
|
||||||
digitalWrite(WUP_PIN, HIGH); // Wake up the battery
|
digitalWrite(WUP_PIN, HIGH); // Wake up the battery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "../include.h"
|
#include "../include.h"
|
||||||
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||||
|
#include "../lib/pierremolinaro-acan2515/ACAN2515.h"
|
||||||
|
|
||||||
|
extern ACAN2515 can;
|
||||||
|
|
||||||
#define BATTERY_SELECTED
|
#define BATTERY_SELECTED
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,8 @@ typedef struct {
|
||||||
#endif
|
#endif
|
||||||
/** True if the battery allows for the contactors to close */
|
/** True if the battery allows for the contactors to close */
|
||||||
bool battery_allows_contactor_closing = false;
|
bool battery_allows_contactor_closing = false;
|
||||||
|
/** True if the second battery allows for the contactors to close */
|
||||||
|
bool battery2_allows_contactor_closing = false;
|
||||||
/** True if the inverter allows for the contactors to close */
|
/** True if the inverter allows for the contactors to close */
|
||||||
bool inverter_allows_contactor_closing = true;
|
bool inverter_allows_contactor_closing = true;
|
||||||
} DATALAYER_SYSTEM_STATUS_TYPE;
|
} DATALAYER_SYSTEM_STATUS_TYPE;
|
||||||
|
@ -158,6 +160,7 @@ typedef struct {
|
||||||
class DataLayer {
|
class DataLayer {
|
||||||
public:
|
public:
|
||||||
DATALAYER_BATTERY_TYPE battery;
|
DATALAYER_BATTERY_TYPE battery;
|
||||||
|
DATALAYER_BATTERY_TYPE battery2;
|
||||||
DATALAYER_SYSTEM_TYPE system;
|
DATALAYER_SYSTEM_TYPE system;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,7 @@ void init_events(void) {
|
||||||
events.entries[EVENT_CANFD_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_CANFD_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_CAN_RX_FAILURE].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_CAN_RX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||||
|
events.entries[EVENT_CAN2_RX_FAILURE].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_CANFD_RX_FAILURE].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_CANFD_RX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||||
|
@ -194,6 +195,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
||||||
return "CAN message failed to send within defined time. Contact developers, CPU load might be too high.";
|
return "CAN message failed to send within defined time. Contact developers, CPU load might be too high.";
|
||||||
case EVENT_CAN_RX_FAILURE:
|
case EVENT_CAN_RX_FAILURE:
|
||||||
return "No CAN communication detected for 60s. Shutting down battery control.";
|
return "No CAN communication detected for 60s. Shutting down battery control.";
|
||||||
|
case EVENT_CAN2_RX_FAILURE:
|
||||||
|
return "No CAN communication detected for 60s on CAN2. Shutting down the secondary battery control.";
|
||||||
case EVENT_CANFD_RX_FAILURE:
|
case EVENT_CANFD_RX_FAILURE:
|
||||||
return "No CANFD communication detected for 60s. Shutting down battery control.";
|
return "No CANFD communication detected for 60s. Shutting down battery control.";
|
||||||
case EVENT_CAN_RX_WARNING:
|
case EVENT_CAN_RX_WARNING:
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
||||||
|
|
||||||
#define EE_MAGIC_HEADER_VALUE 0x0002 // 0x0000 to 0xFFFF
|
#define EE_MAGIC_HEADER_VALUE 0x0003 // 0x0000 to 0xFFFF
|
||||||
|
|
||||||
#define GENERATE_ENUM(ENUM) ENUM,
|
#define GENERATE_ENUM(ENUM) ENUM,
|
||||||
#define GENERATE_STRING(STRING) #STRING,
|
#define GENERATE_STRING(STRING) #STRING,
|
||||||
|
@ -29,6 +29,7 @@
|
||||||
XX(EVENT_CANFD_INIT_FAILURE) \
|
XX(EVENT_CANFD_INIT_FAILURE) \
|
||||||
XX(EVENT_CAN_OVERRUN) \
|
XX(EVENT_CAN_OVERRUN) \
|
||||||
XX(EVENT_CAN_RX_FAILURE) \
|
XX(EVENT_CAN_RX_FAILURE) \
|
||||||
|
XX(EVENT_CAN2_RX_FAILURE) \
|
||||||
XX(EVENT_CANFD_RX_FAILURE) \
|
XX(EVENT_CANFD_RX_FAILURE) \
|
||||||
XX(EVENT_CAN_RX_WARNING) \
|
XX(EVENT_CAN_RX_WARNING) \
|
||||||
XX(EVENT_CAN_TX_FAILURE) \
|
XX(EVENT_CAN_TX_FAILURE) \
|
||||||
|
|
|
@ -466,6 +466,9 @@ String processor(const String& var) {
|
||||||
#endif
|
#endif
|
||||||
#ifdef TEST_FAKE_BATTERY
|
#ifdef TEST_FAKE_BATTERY
|
||||||
content += "Fake battery for testing purposes";
|
content += "Fake battery for testing purposes";
|
||||||
|
#endif
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
content += " (Double battery)";
|
||||||
#endif
|
#endif
|
||||||
content += "</h4>";
|
content += "</h4>";
|
||||||
|
|
||||||
|
@ -483,8 +486,15 @@ String processor(const String& var) {
|
||||||
// Close the block
|
// Close the block
|
||||||
content += "</div>";
|
content += "</div>";
|
||||||
|
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
// Start a new block with a specific background color. Color changes depending on BMS status
|
||||||
|
content += "<div style='display: flex; width: 100%;'>";
|
||||||
|
content += "<div style='flex: 1; background-color: ";
|
||||||
|
#else
|
||||||
// Start a new block with a specific background color. Color changes depending on BMS status
|
// Start a new block with a specific background color. Color changes depending on BMS status
|
||||||
content += "<div style='background-color: ";
|
content += "<div style='background-color: ";
|
||||||
|
#endif
|
||||||
|
|
||||||
switch (led_get_color()) {
|
switch (led_get_color()) {
|
||||||
case led_color::GREEN:
|
case led_color::GREEN:
|
||||||
content += "#2D3F2F;";
|
content += "#2D3F2F;";
|
||||||
|
@ -569,6 +579,84 @@ String processor(const String& var) {
|
||||||
// Close the block
|
// Close the block
|
||||||
content += "</div>";
|
content += "</div>";
|
||||||
|
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
content += "<div style='flex: 1; background-color: ";
|
||||||
|
switch (datalayer.battery2.status.bms_status) {
|
||||||
|
case ACTIVE:
|
||||||
|
content += "#2D3F2F;";
|
||||||
|
break;
|
||||||
|
case FAULT:
|
||||||
|
content += "#A70107;";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
content += "#2D3F2F;";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Add the common style properties
|
||||||
|
content += "padding: 10px; margin-bottom: 10px; border-radius: 50px;'>";
|
||||||
|
|
||||||
|
// Display battery statistics within this block
|
||||||
|
socRealFloat =
|
||||||
|
static_cast<float>(datalayer.battery2.status.real_soc) / 100.0; // Convert to float and divide by 100
|
||||||
|
socScaledFloat =
|
||||||
|
static_cast<float>(datalayer.battery2.status.reported_soc) / 100.0; // Convert to float and divide by 100
|
||||||
|
sohFloat = static_cast<float>(datalayer.battery2.status.soh_pptt) / 100.0; // Convert to float and divide by 100
|
||||||
|
voltageFloat =
|
||||||
|
static_cast<float>(datalayer.battery2.status.voltage_dV) / 10.0; // Convert to float and divide by 10
|
||||||
|
currentFloat =
|
||||||
|
static_cast<float>(datalayer.battery2.status.current_dA) / 10.0; // Convert to float and divide by 10
|
||||||
|
powerFloat = static_cast<float>(datalayer.battery2.status.active_power_W); // Convert to float
|
||||||
|
tempMaxFloat = static_cast<float>(datalayer.battery2.status.temperature_max_dC) / 10.0; // Convert to float
|
||||||
|
tempMinFloat = static_cast<float>(datalayer.battery2.status.temperature_min_dC) / 10.0; // Convert to float
|
||||||
|
|
||||||
|
content += "<h4 style='color: white;'>Real SOC: " + String(socRealFloat, 2) + "</h4>";
|
||||||
|
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) + "</h4>";
|
||||||
|
content += "<h4 style='color: white;'>SOH: " + String(sohFloat, 2) + "</h4>";
|
||||||
|
content += "<h4 style='color: white;'>Voltage: " + String(voltageFloat, 1) + " V</h4>";
|
||||||
|
content += "<h4 style='color: white;'>Current: " + String(currentFloat, 1) + " A</h4>";
|
||||||
|
content += formatPowerValue("Power", powerFloat, "", 1);
|
||||||
|
content += formatPowerValue("Total capacity", datalayer.battery2.info.total_capacity_Wh, "h", 0);
|
||||||
|
content += formatPowerValue("Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
|
||||||
|
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1);
|
||||||
|
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1);
|
||||||
|
content += "<h4>Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
|
||||||
|
content += "<h4>Cell min: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV</h4>";
|
||||||
|
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
|
||||||
|
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
|
||||||
|
if (datalayer.battery2.status.bms_status == ACTIVE) {
|
||||||
|
content += "<h4>BMS Status: OK </h4>";
|
||||||
|
} else if (datalayer.battery2.status.bms_status == UPDATING) {
|
||||||
|
content += "<h4>BMS Status: UPDATING </h4>";
|
||||||
|
} else {
|
||||||
|
content += "<h4>BMS Status: FAULT </h4>";
|
||||||
|
}
|
||||||
|
if (datalayer.battery2.status.current_dA == 0) {
|
||||||
|
content += "<h4>Battery idle</h4>";
|
||||||
|
} else if (datalayer.battery2.status.current_dA < 0) {
|
||||||
|
content += "<h4>Battery discharging!</h4>";
|
||||||
|
} else { // > 0
|
||||||
|
content += "<h4>Battery charging!</h4>";
|
||||||
|
}
|
||||||
|
|
||||||
|
content += "<h4>Automatic contactor closing allowed:</h4>";
|
||||||
|
content += "<h4>Battery: ";
|
||||||
|
if (datalayer.system.status.battery2_allows_contactor_closing == true) {
|
||||||
|
content += "<span>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
content += " Inverter: ";
|
||||||
|
if (datalayer.system.status.inverter_allows_contactor_closing == true) {
|
||||||
|
content += "<span>✓</span></h4>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span></h4>";
|
||||||
|
}
|
||||||
|
|
||||||
|
content += "</div>";
|
||||||
|
content += "</div>";
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
|
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
|
||||||
// Start a new block with orange background color
|
// Start a new block with orange background color
|
||||||
content += "<div style='background-color: #FF6E00; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
|
content += "<div style='background-color: #FF6E00; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue