Merge branch 'main' into feature/PSA-battery

This commit is contained in:
Daniel Öster 2024-12-28 15:51:19 +03:00
commit fe4bd981cc
150 changed files with 18379 additions and 3859 deletions

View file

@ -6,12 +6,25 @@
#include "BMW-I3-BATTERY.h"
#endif
#ifdef BMW_IX_BATTERY
#include "BMW-IX-BATTERY.h"
#endif
#ifdef BOLT_AMPERA_BATTERY
#include "BOLT-AMPERA-BATTERY.h"
#endif
#ifdef BYD_ATTO_3_BATTERY
#include "BYD-ATTO-3-BATTERY.h"
#endif
#ifdef CELLPOWER_BMS
#include "CELLPOWER-BMS.h"
#endif
#ifdef CHADEMO_BATTERY
#include "CHADEMO-BATTERY.h"
#include "CHADEMO-SHUNTS.h"
#endif
#ifdef ECMP_BATTERY
@ -38,6 +51,10 @@
#include "KIA-HYUNDAI-HYBRID-BATTERY.h"
#endif
#ifdef MEB_BATTERY
#include "MEB-BATTERY.h"
#endif
#ifdef MG_5_BATTERY
#include "MG-5-BATTERY.h"
#endif
@ -54,10 +71,18 @@
#include "RJXZS-BMS.h"
#endif
#ifdef RANGE_ROVER_PHEV_BATTERY
#include "RANGE-ROVER-PHEV-BATTERY.h"
#endif
#ifdef RENAULT_KANGOO_BATTERY
#include "RENAULT-KANGOO-BATTERY.h"
#endif
#ifdef RENAULT_TWIZY_BATTERY
#include "RENAULT-TWIZY.h"
#endif
#ifdef RENAULT_ZOE_GEN1_BATTERY
#include "RENAULT-ZOE-GEN1-BATTERY.h"
#endif

View file

@ -1,6 +1,7 @@
#include "../include.h"
#ifdef BMW_I3_BATTERY
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h"
#include "BMW-I3-BATTERY.h"
@ -238,7 +239,6 @@ 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_power = 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;
@ -307,7 +307,6 @@ 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;
@ -387,19 +386,50 @@ void update_values_battery2() { //This function maps all the values fetched via
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];
if (battery2_info_available) {
// Start checking safeties. First up, cellvoltages!
if (detectedBattery == 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 (detectedBattery == 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
if (datalayer.system.settings.equipment_stop_active == true) {
digitalWrite(WUP_PIN, LOW); // Turn off WUP_PIN
} else {
digitalWrite(WUP_PIN, HIGH); // Wake up the battery
}
if (!battery_awake) {
return;
}
@ -420,10 +450,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
datalayer.battery.status.active_power_W = battery_power;
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
@ -433,30 +459,18 @@ void update_values_battery() { //This function maps all the values fetched via
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;
if (datalayer.battery.status.cell_max_voltage_mV >= MAX_CELL_VOLTAGE_60AH) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (datalayer.battery.status.cell_min_voltage_mV <= MIN_CELL_VOLTAGE_60AH) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
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;
if (datalayer.battery.status.cell_max_voltage_mV >= MAX_CELL_VOLTAGE_94AH) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (datalayer.battery.status.cell_min_voltage_mV <= MIN_CELL_VOLTAGE_94AH) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
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;
if (datalayer.battery.status.cell_max_voltage_mV >= MAX_CELL_VOLTAGE_120AH) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (datalayer.battery.status.cell_min_voltage_mV <= MIN_CELL_VOLTAGE_120AH) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_120AH;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_120AH;
}
}
@ -466,39 +480,26 @@ void update_values_battery() { //This function maps all the values fetched via
} else {
clear_event(EVENT_HVIL_FAILURE);
}
if (battery_status_precharge_locked == 2) { // Capacitor seated?
set_event(EVENT_PRECHARGE_FAILURE, 0);
if (battery_status_error_disconnecting_switch > 0) { // Check if contactors are sticking / welded
set_event(EVENT_CONTACTOR_WELDED, 0);
} else {
clear_event(EVENT_PRECHARGE_FAILURE);
clear_event(EVENT_CONTACTOR_WELDED);
}
#ifdef DEBUG_VIA_USB
Serial.println(" ");
Serial.print("Battery display SOC%: ");
Serial.print(battery_display_SOC * 50);
Serial.print("Battery display SOC%: ");
Serial.print(battery_HVBatt_SOC * 10);
Serial.print("Battery polled SOC%: ");
Serial.print(battery_soc);
Serial.print(" Battery voltage: ");
Serial.print(datalayer.battery.status.voltage_dV * 0.1);
Serial.print(" Battery current: ");
Serial.print(datalayer.battery.status.current_dA * 0.1);
Serial.print(" Wh when full: ");
Serial.print(datalayer.battery.info.total_capacity_Wh);
Serial.print(" Remaining Wh: ");
Serial.print(datalayer.battery.status.remaining_capacity_Wh);
Serial.print(" Max charge power: ");
Serial.print(datalayer.battery.status.max_charge_power_W);
Serial.print(" Max discharge power: ");
Serial.print(datalayer.battery.status.max_discharge_power_W);
Serial.print(" Active power: ");
Serial.print(datalayer.battery.status.active_power_W);
Serial.print(" Min temp: ");
Serial.print(datalayer.battery.status.temperature_min_dC * 0.1);
Serial.print(" Max temp: ");
Serial.print(datalayer.battery.status.temperature_max_dC * 0.1);
#endif
// Update webserver datalayer
datalayer_extended.bmwi3.SOC_raw = (battery_HVBatt_SOC * 10);
datalayer_extended.bmwi3.SOC_dash = (battery_display_SOC * 50);
datalayer_extended.bmwi3.SOC_OBD2 = battery_soc;
datalayer_extended.bmwi3.ST_iso_ext = battery_status_error_isolation_external_Bordnetz;
datalayer_extended.bmwi3.ST_iso_int = battery_status_error_isolation_internal_Bordnetz;
datalayer_extended.bmwi3.ST_valve_cooling = battery_status_valve_cooling;
datalayer_extended.bmwi3.ST_interlock = battery_status_error_locking;
datalayer_extended.bmwi3.ST_precharge = battery_status_precharge_locked;
datalayer_extended.bmwi3.ST_DCSW = battery_status_disconnecting_switch;
datalayer_extended.bmwi3.ST_EMG = battery_status_emergency_mode;
datalayer_extended.bmwi3.ST_WELD = battery_status_error_disconnecting_switch;
datalayer_extended.bmwi3.ST_isolation = battery_status_warning_isolation;
datalayer_extended.bmwi3.ST_cold_shutoff_valve = battery_status_cold_shutoff_valve;
}
void receive_can_battery(CAN_frame rx_frame) {
@ -784,7 +785,7 @@ void receive_can_battery2(CAN_frame rx_frame) {
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);
datalayer.battery2.status.cell_voltages_mV[6] = ((rx_frame.data.u8[7] * 10) + 1800);
}
break;
case 0x430: //BMS [1s] - Charging status of high-voltage battery - 2
@ -1117,20 +1118,19 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("BMW i3 battery selected");
#endif
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.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.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
#endif

View file

@ -0,0 +1,794 @@
#include "../include.h"
#ifdef BMW_IX_BATTERY
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h"
#include "BMW-IX-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 CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
static CmdState cmdState = SOC;
/*
Suspected Vehicle comms required:
0x06D DLC? 1000ms - counters?
0x2F1 DLC? 1000ms during run : 0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF - at startup 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF3, 0xFF. Suspect byte [4] is a counter
0x439 DLC4 1000ms STATIC
0x0C0 DLC2 200ms needs counter
0x587 DLC8 appears at startup 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF , 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF, 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF, 0x06 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x82 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF
SME Output:
0x08F DLC48 10ms - Appears to have analog readings like volt/temp/current
0x12B8D087 5000ms - Extended ID
0x1D2 DLC8 1000ms
0x20B DLC8 1000ms
0x2E2 DLC16 1000ms
0x2F1 DLC8 1000ms
0x31F DLC16 100ms - 2 downward counters?
0x453 DLC20 200ms
0x486 DLC48 1000ms
0x49C DLC8 1000ms
0x4A1 DLC8 1000ms
0x4BB DLC64 200ms - seems multplexed on [0]
0x4D0 DLC64 1000ms - some slow/flickering values - possible change during fault
0x510 DLC8 100ms STATIC 40 10 40 00 6F DF 19 00 during run - Startup sends this once: 0x40 0x10 0x02 0x00 0x00 0x00 0x00 0x00
0x607 UDS Response
No vehicle log available, SME asks for:
0x125 (CCU)
0x16E (CCU)
0x340 (CCU)
0x4F8 (CCU)
0x188 (CCU)
0x91 (EME1)
0xAA (EME2)
0x?? Suspect there is a drive mode flag somewhere - balancing might only be active in some modes
TODO
- Request batt serial number on F1 8C (already parsing RX)
*/
//Vehicle CAN START
CAN_frame BMWiX_06D = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x06D,
.data = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0xFF}}; // 1000ms BDC Output - [0] static [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static
CAN_frame BMWiX_0C0 = {
.FD = true,
.ext_ID = false,
.DLC = 2,
.ID = 0x0C0,
.data = {
0xF0,
0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static - MINIMUM ID TO KEEP SME AWAKE
CAN_frame BMWiX_276 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x476,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFC}}; // 5000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED
CAN_frame BMWiX_2F1 = {
.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x2F1,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF}}; // 1000ms BDC Output - Static values - varies at startup
CAN_frame BMWiX_439 = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x439,
.data = {0xFF, 0xBF, 0xFF, 0xFF}}; // 1000ms BDC Output - Static values
CAN_frame
BMWiX_486 =
{
.FD = true,
.ext_ID = false,
.DLC = 48,
.ID = 0x486,
.data =
{
0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF,
0xFE, 0xFF, 0xFF, 0x7F, 0x33, 0xFD, 0xFD, 0xFD, 0xFD, 0xC0, 0x41, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED
CAN_frame BMWiX_49C = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x49C,
.data = {0xD2, 0xF2, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED
CAN_frame BMWiX_510 = {.FD = true,
.ext_ID = false,
.DLC = 8,
.ID = 0x510,
.data = {0x40, 0x10, 0x40, 0x00, 0x6F, 0xDF, 0x19, 0x00}}; // 100ms BDC Output - Static values
CAN_frame BMWiX_12B8D087 = {.FD = true,
.ext_ID = true,
.DLC = 2,
.ID = 0x12B8D087,
.data = {0xFC, 0xFF}}; // 5000ms SME Output - Static values
//Vehicle CAN END
//Request Data CAN START
CAN_frame BMWiX_6F4 = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value
CAN_frame BMWiX_6F4_REQUEST_SLEEPMODE = {
.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F4,
.data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode
CAN_frame BMWiX_6F4_REQUEST_HARD_RESET = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F4,
.data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME
CAN_frame BMWiX_6F4_REQUEST_CELL_TEMP = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xDD, 0xC0}}; // UDS Request Cell Temperatures
CAN_frame BMWiX_6F4_REQUEST_SOC = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xCE}}; // Min/Avg/Max SOC%
CAN_frame BMWiX_6F4_REQUEST_CAPACITY = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias
CAN_frame BMWiX_6F4_REQUEST_MINMAXCELLV = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x53}}; //Min and max cell voltage 10V = Qualifier Invalid
CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4A}}; //Main Battery Voltage (After Contactor)
CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4D}}; //Main Battery Voltage (Pre Contactor)
CAN_frame BMWiX_6F4_REQUEST_BATTERYCURRENT = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge
CAN_frame BMWiX_6F4_REQUEST_CELL_VOLTAGE = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages
CAN_frame BMWiX_6F4_REQUEST_T30VOLTAGE = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply)
CAN_frame BMWiX_6F4_REQUEST_EOL_ISO = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO
CAN_frame BMWiX_6F4_REQUEST_SOH = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request
CAN_frame BMWiX_6F4_REQUEST_DATASUMMARY = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {
0x07, 0x03, 0x22, 0xE5,
0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate
CAN_frame BMWiX_6F4_REQUEST_PYRO = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status
CAN_frame BMWiX_6F4_REQUEST_UPTIME = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status
CAN_frame BMWiX_6F4_REQUEST_HVIL = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State
CAN_frame BMWiX_6F4_REQUEST_BALANCINGSTATUS = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data
CAN_frame BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps
CAN_frame BMWiX_6F4_REQUEST_VOLTAGE_QUALIFIER_CHECK = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier
CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_CLOSE = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Close - Unconfirmed
CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_OPEN = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Open - Unconfirmed
CAN_frame BMWiX_6F4_REQUEST_BALANCING_START = {
.FD = true,
.ext_ID = false,
.DLC = 6,
.ID = 0x6F4,
.data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command?
CAN_frame BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS = {
.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits
CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true,
.ext_ID = false,
.DLC = 4,
.ID = 0x6F4,
.data = {0x07, 0x30, 0x00, 0x02}};
//Action Requests:
CAN_frame BMW_10B = {.FD = true,
.ext_ID = false,
.DLC = 3,
.ID = 0x10B,
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command?
CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.data = {0x07, 0x03, 0x22, 0xE5, 0x9A}};
CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true,
.ext_ID = false,
.DLC = 5,
.ID = 0x6F4,
.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,
&BMWiX_6F4_REQUEST_CAPACITY,
&BMWiX_6F4_REQUEST_MINMAXCELLV,
&BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR,
&BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR,
&BMWiX_6F4_REQUEST_BATTERYCURRENT,
&BMWiX_6F4_REQUEST_CELL_VOLTAGE,
&BMWiX_6F4_REQUEST_T30VOLTAGE,
&BMWiX_6F4_REQUEST_SOH,
&BMWiX_6F4_REQUEST_UPTIME,
&BMWiX_6F4_REQUEST_PYRO,
&BMWiX_6F4_REQUEST_EOL_ISO,
&BMWiX_6F4_REQUEST_HVIL,
&BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS,
&BMWiX_6F4_REQUEST_BALANCINGSTATUS,
&BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS};
int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array
//iX Intermediate vars
static bool battery_info_available = false;
static uint32_t battery_serial_number = 0;
static int32_t battery_current = 0;
static int16_t battery_voltage = 370;
static int16_t terminal30_12v_voltage = 0;
static int16_t battery_voltage_after_contactor = 0;
static int16_t min_soc_state = 50;
static int16_t avg_soc_state = 50;
static int16_t max_soc_state = 50;
static int16_t min_soh_state = 99; // Uses E5 45, also available in 78 73
static int16_t avg_soh_state = 99; // Uses E5 45, also available in 78 73
static int16_t max_soh_state = 99; // Uses E5 45, also available in 78 73
static uint16_t max_design_voltage = 0;
static uint16_t min_design_voltage = 0;
static int32_t remaining_capacity = 0;
static int32_t max_capacity = 0;
static int16_t min_battery_temperature = 0;
static int16_t avg_battery_temperature = 0;
static int16_t max_battery_temperature = 0;
static int16_t main_contactor_temperature = 0;
static int16_t min_cell_voltage = 0;
static int16_t max_cell_voltage = 0;
static unsigned long min_cell_voltage_lastchanged = 0;
static unsigned long max_cell_voltage_lastchanged = 0;
static unsigned min_cell_voltage_lastreceived = 0;
static unsigned max_cell_voltage_lastreceived = 0;
static uint32_t sme_uptime = 0; //Uses E4 C0
static int16_t allowable_charge_amps = 0; //E5 62
static int16_t allowable_discharge_amps = 0; //E5 62
static int32_t iso_safety_positive = 0; //Uses A8 60
static int32_t iso_safety_negative = 0; //Uses A8 60
static int32_t iso_safety_parallel = 0; //Uses A8 60
static int16_t count_full_charges = 0; //TODO 42
static int16_t count_charges = 0; //TODO 42
static int16_t hvil_status = 0;
static int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid
static int16_t balancing_status = 0; //4 = not active
static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51
static uint8_t contactor_status_precharge = 0; //TODO E5 BF
static uint8_t contactor_status_negative = 0; //TODO E5 BF
static uint8_t contactor_status_positive = 0; //TODO E5 BF
static uint8_t pyro_status_pss1 = 0; //Using AC 93
static uint8_t pyro_status_pss4 = 0; //Using AC 93
static uint8_t pyro_status_pss6 = 0; //Using AC 93
static uint8_t uds_req_id_counter = 0;
static uint8_t detected_number_of_cells = 108;
const unsigned long STALE_PERIOD =
STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds)
static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0
//End iX Intermediate vars
static uint8_t current_cell_polled = 0;
// 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();
// Check if the value has changed
if (currentValue != lastValue) {
// Update the last change time and value
lastChangeTime = currentTime;
lastValue = currentValue;
return false; // Value is fresh because it has changed
}
// Check if the value has stayed the same for the specified staleness period
return (currentTime - lastChangeTime >= STALE_PERIOD);
}
static uint8_t increment_uds_req_id_counter(uint8_t index) {
index++;
if (index >= numUDSreqs) {
index = 0;
}
return index;
}
static uint8_t increment_alive_counter(uint8_t counter) {
counter++;
if (counter > ALIVE_MAX_VALUE) {
counter = 0;
}
return counter;
}
static byte increment_0C0_counter(byte counter) {
counter++;
// Reset to 0xF0 if it exceeds 0xFE
if (counter > 0xFE) {
counter = 0xF0;
}
return counter;
}
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
datalayer.battery.status.real_soc = avg_soc_state;
datalayer.battery.status.voltage_dV = battery_voltage;
datalayer.battery.status.current_dA = battery_current;
datalayer.battery.info.total_capacity_Wh = max_capacity;
datalayer.battery.status.remaining_capacity_Wh = remaining_capacity;
datalayer.battery.status.soh_pptt = min_soh_state;
datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W;
//datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping
// Charge power is set in .h file
if (datalayer.battery.status.real_soc > 9900) {
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W;
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
datalayer.battery.status.max_charge_power_W =
MAX_CHARGE_POWER_ALLOWED_W *
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
} else { // No limits, max charging power allowed
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W;
}
datalayer.battery.status.temperature_min_dC = min_battery_temperature;
datalayer.battery.status.temperature_max_dC = max_battery_temperature;
//Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze.
bool isMinCellVoltageStale =
isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged);
bool isMaxCellVoltageStale =
isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged);
if (isMinCellVoltageStale && isMaxCellVoltageStale) {
datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop
datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive
datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive
}
datalayer.battery.info.max_design_voltage_dV = max_design_voltage;
datalayer.battery.info.min_design_voltage_dV = min_design_voltage;
datalayer.battery.info.number_of_cells = detected_number_of_cells;
datalayer_extended.bmwix.min_cell_voltage_data_age = (millis() - min_cell_voltage_lastchanged);
datalayer_extended.bmwix.max_cell_voltage_data_age = (millis() - max_cell_voltage_lastchanged);
datalayer_extended.bmwix.T30_Voltage = terminal30_12v_voltage;
datalayer_extended.bmwix.hvil_status = hvil_status;
datalayer_extended.bmwix.bms_uptime = sme_uptime;
datalayer_extended.bmwix.pyro_status_pss1 = pyro_status_pss1;
datalayer_extended.bmwix.pyro_status_pss4 = pyro_status_pss4;
datalayer_extended.bmwix.pyro_status_pss6 = pyro_status_pss6;
datalayer_extended.bmwix.iso_safety_positive = iso_safety_positive;
datalayer_extended.bmwix.iso_safety_negative = iso_safety_negative;
datalayer_extended.bmwix.iso_safety_parallel = iso_safety_parallel;
datalayer_extended.bmwix.allowable_charge_amps = allowable_charge_amps;
datalayer_extended.bmwix.allowable_discharge_amps = allowable_discharge_amps;
datalayer_extended.bmwix.balancing_status = balancing_status;
datalayer_extended.bmwix.battery_voltage_after_contactor = battery_voltage_after_contactor;
if (battery_info_available) {
// If we have data from battery - override the defaults to suit
datalayer.battery.info.max_design_voltage_dV = max_design_voltage;
datalayer.battery.info.min_design_voltage_dV = min_design_voltage;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
}
void receive_can_battery(CAN_frame rx_frame) {
battery_awake = true;
switch (rx_frame.ID) {
case 0x112:
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
if (rx_frame.DLC = 64 && rx_frame.data.u8[5] == 0x54) { //Individual Cell Voltages - First Frame
int start_index = 6; //Data starts here
int voltage_index = 0; //Start cell ID
int num_voltages = 29; // number of voltage readings to get
for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
if (voltage < 10000) { //Check reading is plausible - otherwise ignore
datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
}
voltage_index++;
}
}
//Frame has continued data - so request it
transmit_can(&BMWiX_6F4_CONTINUE_DATA, can_config.battery);
}
if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 &&
rx_frame.data.u8[1] == 0x21) { //Individual Cell Voltages - 1st Continue frame
int start_index = 2; //Data starts here
int voltage_index = 29; //Start cell ID
int num_voltages = 31; // number of voltage readings to get
for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
if (voltage < 10000) { //Check reading is plausible - otherwise ignore
datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
}
voltage_index++;
}
}
if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 &&
rx_frame.data.u8[1] == 0x22) { //Individual Cell Voltages - 2nd Continue frame
int start_index = 2; //Data starts here
int voltage_index = 60; //Start cell ID
int num_voltages = 31; // number of voltage readings to get
for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
if (voltage < 10000) { //Check reading is plausible - otherwise ignore
datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
}
voltage_index++;
}
}
if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 &&
rx_frame.data.u8[1] == 0x23) { //Individual Cell Voltages - 3rd Continue frame
int start_index = 2; //Data starts here
int voltage_index = 91; //Start cell ID
int num_voltages;
if (rx_frame.data.u8[12] == 0xFF && rx_frame.data.u8[13] == 0xFF) { //97th cell is blank - assume 96S Battery
num_voltages = 5; // number of voltage readings to get - 6 more to get on 96S
detected_number_of_cells = 96;
} else { //We have data in 97th cell, assume 108S Battery
num_voltages = 17; // number of voltage readings to get - 17 more to get on 108S
detected_number_of_cells = 108;
}
for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
if (voltage < 10000) { //Check reading is plausible - otherwise ignore
datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
}
voltage_index++;
}
}
if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4D) { //Main Battery Voltage (Pre Contactor)
battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10;
}
if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4A) { //Main Battery Voltage (After Contactor)
battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10;
}
if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 &&
rx_frame.data.u8[5] == 0x61) { //Current amps 32bit signed MSB. dA . negative is discharge
battery_current = ((int32_t)((rx_frame.data.u8[6] << 24) | (rx_frame.data.u8[7] << 16) |
(rx_frame.data.u8[8] << 8) | rx_frame.data.u8[9])) *
0.1;
}
if (rx_frame.DLC = 64 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xCA) { //Balancing Data
balancing_status = (rx_frame.data.u8[6]); //4 = No symmetry mode active, invalid qualifier
}
if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xCE) { //Min/Avg/Max SOC%
min_soc_state = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]);
avg_soc_state = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
max_soc_state = (rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]);
}
if (rx_frame.DLC =
12 && rx_frame.data.u8[4] == 0xE5 &&
rx_frame.data.u8[5] == 0xC7) { //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias
remaining_capacity = ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) * 10) - 50000;
max_capacity = ((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) * 10) - 50000;
}
if (rx_frame.DLC = 20 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x45) { //SOH Max Min Mean Request
min_soh_state = ((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]));
avg_soh_state = ((rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]));
max_soh_state = ((rx_frame.data.u8[12] << 8 | rx_frame.data.u8[13]));
}
if (rx_frame.DLC = 10 && rx_frame.data.u8[4] == 0xE5 &&
rx_frame.data.u8[5] == 0x62) { //Max allowed charge and discharge current - Signed 16bit
allowable_charge_amps = (int16_t)((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7])) / 10;
allowable_discharge_amps = (int16_t)((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9])) / 10;
}
if (rx_frame.DLC = 9 && rx_frame.data.u8[4] == 0xE5 &&
rx_frame.data.u8[5] == 0x4B) { //Max allowed charge and discharge current - Signed 16bit
voltage_qualifier_status = (rx_frame.data.u8[8]); // Request HV Voltage Qualifier
}
if (rx_frame.DLC =
48 && rx_frame.data.u8[4] == 0xA8 && rx_frame.data.u8[5] == 0x60) { // Safety Isolation Measurements
iso_safety_positive = (rx_frame.data.u8[34] << 24) | (rx_frame.data.u8[35] << 16) |
(rx_frame.data.u8[36] << 8) | rx_frame.data.u8[37]; //Assuming 32bit
iso_safety_negative = (rx_frame.data.u8[38] << 24) | (rx_frame.data.u8[39] << 16) |
(rx_frame.data.u8[40] << 8) | rx_frame.data.u8[41]; //Assuming 32bit
iso_safety_parallel = (rx_frame.data.u8[42] << 24) | (rx_frame.data.u8[43] << 16) |
(rx_frame.data.u8[44] << 8) | rx_frame.data.u8[45]; //Assuming 32bit
}
if (rx_frame.DLC =
48 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xC0) { // Uptime and Vehicle Time Status
sme_uptime = (rx_frame.data.u8[10] << 24) | (rx_frame.data.u8[11] << 16) | (rx_frame.data.u8[12] << 8) |
rx_frame.data.u8[13]; //Assuming 32bit
}
if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xAC && rx_frame.data.u8[4] == 0x93) { // Pyro Status
pyro_status_pss1 = (rx_frame.data.u8[5]);
pyro_status_pss4 = (rx_frame.data.u8[6]);
pyro_status_pss6 = (rx_frame.data.u8[7]);
}
if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 &&
rx_frame.data.u8[5] == 0x53) { //Min and max cell voltage 10V = Qualifier Invalid
datalayer.battery.status.CAN_battery_still_alive =
CAN_STILL_ALIVE; //This is the most important safety values, if we receive this we reset CAN alive counter.
if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) == 10000 ||
(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
//set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type
transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
} else { //Only ingest values if they are not the 10V Error state
min_cell_voltage = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
max_cell_voltage = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]);
}
}
if (rx_frame.DLC = 16 && rx_frame.data.u8[4] == 0xDD && rx_frame.data.u8[5] == 0xC0) { //Battery Temperature
min_battery_temperature = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) / 10;
avg_battery_temperature = (rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]) / 10;
max_battery_temperature = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) / 10;
}
if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA3) { //Main Contactor Temperature CHECK FINGERPRINT 2 LEVEL
main_contactor_temperature = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]);
}
if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA7) { //Terminal 30 Voltage (12V SME supply)
terminal30_12v_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]);
}
if (rx_frame.DLC = 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x04 &&
rx_frame.data.u8[2] == 0x62 && rx_frame.data.u8[3] == 0xE5 &&
rx_frame.data.u8[4] == 0x69) { //HVIL Status
hvil_status = (rx_frame.data.u8[5]);
}
if (rx_frame.DLC = 12 && rx_frame.data.u8[2] == 0x07 && rx_frame.data.u8[3] == 0x62 &&
rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x4C) { //Pack Voltage Limits
if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) < 4700 &&
(rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) > 2600) { //Make sure values are plausible
battery_info_available = true;
max_design_voltage = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
min_design_voltage = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]);
}
}
if (rx_frame.DLC = 16 && rx_frame.data.u8[3] == 0xF1 && rx_frame.data.u8[4] == 0x8C) { //Battery Serial Number
//Convert hex bytes to ASCII characters and combine them into a string
char numberString[11]; // 10 characters + null terminator
for (int i = 0; i < 10; i++) {
numberString[i] = char(rx_frame.data.u8[i + 6]);
}
numberString[10] = '\0'; // Null-terminate the string
// Step 3: Convert the string to an unsigned long integer
battery_serial_number = strtoul(numberString, NULL, 10);
}
break;
default:
break;
}
}
void send_can_battery() {
unsigned long currentMillis = millis();
//if (battery_awake) { //We can always send CAN as the iX BMS will wake up on vehicle comms
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
//Loop through and send a different UDS request each cycle
uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
transmit_can(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery);
//Send SME Keep alive values 100ms
transmit_can(&BMWiX_510, can_config.battery);
}
// Send 200ms CAN Message
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
previousMillis200 = currentMillis;
//Send SME Keep alive values 200ms
BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1
transmit_can(&BMWiX_0C0, can_config.battery);
}
// Send 1000ms CAN Message
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
previousMillis1000 = currentMillis;
//Send SME Keep alive values 1000ms
//Don't believe this is needed: transmit_can(&BMWiX_06D, can_config.battery);
//Don't believe this is needed: transmit_can(&BMWiX_2F1, can_config.battery);
//Don't believe this is needed: transmit_can(&BMWiX_439, can_config.battery);
}
// Send 5000ms CAN Message
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis;
}
// Send 10000ms CAN Message
if (currentMillis - previousMillis10000 >= INTERVAL_10_S) {
previousMillis10000 = currentMillis;
}
}
//We can always send CAN as the iX BMS will wake up on vehicle comms
// else {
// previousMillis20 = currentMillis;
// previousMillis100 = currentMillis;
// previousMillis200 = currentMillis;
// previousMillis500 = currentMillis;
// previousMillis640 = currentMillis;
// previousMillis1000 = currentMillis;
// previousMillis5000 = currentMillis;
// previousMillis10000 = currentMillis;
// }
//} //We can always send CAN as the iX BMS will wake up on vehicle comms
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", 63);
datalayer.system.info.battery_protocol[63] = '\0';
//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;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif

View file

@ -0,0 +1,23 @@
#ifndef BMW_IX_BATTERY_H
#define BMW_IX_BATTERY_H
#include <Arduino.h>
#include "../include.h"
#define BATTERY_SELECTED
//#define WUP_PIN 25 //Not used
#define MAX_PACK_VOLTAGE_DV 4650 //4650 = 465.0V
#define MIN_PACK_VOLTAGE_DV 3000
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4300 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value
#define MAX_DISCHARGE_POWER_ALLOWED_W 10000
#define MAX_CHARGE_POWER_ALLOWED_W 10000
#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500
#define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
#define STALE_PERIOD_CONFIG \
300000; //Number of milliseconds before critical values are classed as stale/stuck 300000 = 300 seconds
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
#endif

View file

@ -0,0 +1,789 @@
#include "../include.h"
#ifdef BOLT_AMPERA_BATTERY
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h"
#include "BOLT-AMPERA-BATTERY.h"
/*
TODOs left for this implementation
- The battery has 3 CAN ports. One of them is responsible for the 7E4 polls, the other for the 7E7 polls
- Current implementation only seems to get the 7E7 polls working.
- Could on of the CAN channels be GMLAN?
- The values missing for a working implementation is:
- SOC% missing! This is absolutely mandatory to fix before starting to use this!
- Capacity (kWh) (can be estimated)
- Charge max power (can be estimated)
- Discharge max power (can be estimated)
- SOH% (low prio))
*/
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send
static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis120ms = 0; // will store last time a 120ms CAN Message was send
CAN_frame BOLT_778 = {.FD = false, // Unsure of what this message is, added only as example
.ext_ID = false,
.DLC = 7,
.ID = 0x778,
.data = {0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_POLL_7E4 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_ACK_7E4 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E4,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_POLL_7E7 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x03, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame BOLT_ACK_7E7 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x7E7,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
// 7E4 Battery , reply 000007EC
// 7E7 Battery (Cell voltages), reply 000007EF
static uint16_t battery_cell_voltages[96]; //array with all the cellvoltages
static uint16_t battery_capacity_my17_18 = 0;
static uint16_t battery_capacity_my19plus = 0;
static uint16_t battery_SOC_display = 0;
static uint16_t battery_SOC_raw_highprec = 0;
static uint16_t battery_max_temperature = 0;
static uint16_t battery_min_temperature = 0;
static uint16_t battery_min_cell_voltage = 0;
static uint16_t battery_max_cell_voltage = 0;
static uint16_t battery_internal_resistance = 0;
static uint16_t battery_lowest_cell = 0;
static uint16_t battery_highest_cell = 0;
static uint16_t battery_voltage_polled = 0;
static uint16_t battery_voltage_periodic = 0;
static uint16_t battery_vehicle_isolation = 0;
static uint16_t battery_isolation_kohm = 0;
static uint16_t battery_HV_locked = 0;
static uint16_t battery_crash_event = 0;
static uint16_t battery_HVIL = 0;
static uint16_t battery_HVIL_status = 0;
static uint16_t battery_5V_ref = 0;
static int16_t battery_current_7E4 = 0;
static int16_t battery_module_temp_1 = 0;
static int16_t battery_module_temp_2 = 0;
static int16_t battery_module_temp_3 = 0;
static int16_t battery_module_temp_4 = 0;
static int16_t battery_module_temp_5 = 0;
static int16_t battery_module_temp_6 = 0;
static uint16_t battery_cell_average_voltage = 0;
static uint16_t battery_cell_average_voltage_2 = 0;
static uint16_t battery_terminal_voltage = 0;
static uint16_t battery_ignition_power_mode = 0;
static int16_t battery_current_7E7 = 0;
static int16_t temperature_1 = 0;
static int16_t temperature_2 = 0;
static int16_t temperature_3 = 0;
static int16_t temperature_4 = 0;
static int16_t temperature_5 = 0;
static int16_t temperature_6 = 0;
static int16_t temperature_highest = 0;
static int16_t temperature_lowest = 0;
static uint8_t mux = 0;
static uint8_t poll_index_7E4 = 0;
static uint16_t currentpoll_7E4 = POLL_7E4_CAPACITY_EST_GEN1;
static uint16_t reply_poll_7E4 = 0;
static uint8_t poll_index_7E7 = 0;
static uint16_t currentpoll_7E7 = POLL_7E7_CURRENT;
static uint16_t reply_poll_7E7 = 0;
const uint16_t poll_commands_7E4[19] = {POLL_7E4_CAPACITY_EST_GEN1,
POLL_7E4_CAPACITY_EST_GEN2,
POLL_7E4_SOC_DISPLAY,
POLL_7E4_SOC_RAW_HIGHPREC,
POLL_7E4_MAX_TEMPERATURE,
POLL_7E4_MIN_TEMPERATURE,
POLL_7E4_MIN_CELL_V,
POLL_7E4_MAX_CELL_V,
POLL_7E4_INTERNAL_RES,
POLL_7E4_LOWEST_CELL_NUMBER,
POLL_7E4_HIGHEST_CELL_NUMBER,
POLL_7E4_VOLTAGE,
POLL_7E4_VEHICLE_ISOLATION,
POLL_7E4_ISOLATION_TEST_KOHM,
POLL_7E4_HV_LOCKED_OUT,
POLL_7E4_CRASH_EVENT,
POLL_7E4_HVIL,
POLL_7E4_HVIL_STATUS,
POLL_7E4_CURRENT};
const uint16_t poll_commands_7E7[108] = {POLL_7E7_CURRENT, POLL_7E7_5V_REF,
POLL_7E7_MODULE_TEMP_1, POLL_7E7_MODULE_TEMP_2,
POLL_7E7_MODULE_TEMP_3, POLL_7E7_MODULE_TEMP_4,
POLL_7E7_MODULE_TEMP_5, POLL_7E7_MODULE_TEMP_6,
POLL_7E7_CELL_AVG_VOLTAGE, POLL_7E7_CELL_AVG_VOLTAGE_2,
POLL_7E7_TERMINAL_VOLTAGE, POLL_7E7_IGNITION_POWER_MODE,
POLL_7E7_CELL_01, POLL_7E7_CELL_02,
POLL_7E7_CELL_03, POLL_7E7_CELL_04,
POLL_7E7_CELL_05, POLL_7E7_CELL_06,
POLL_7E7_CELL_07, POLL_7E7_CELL_08,
POLL_7E7_CELL_09, POLL_7E7_CELL_10,
POLL_7E7_CELL_11, POLL_7E7_CELL_12,
POLL_7E7_CELL_13, POLL_7E7_CELL_14,
POLL_7E7_CELL_15, POLL_7E7_CELL_16,
POLL_7E7_CELL_17, POLL_7E7_CELL_18,
POLL_7E7_CELL_19, POLL_7E7_CELL_20,
POLL_7E7_CELL_21, POLL_7E7_CELL_22,
POLL_7E7_CELL_23, POLL_7E7_CELL_24,
POLL_7E7_CELL_25, POLL_7E7_CELL_26,
POLL_7E7_CELL_27, POLL_7E7_CELL_28,
POLL_7E7_CELL_29, POLL_7E7_CELL_30,
POLL_7E7_CELL_31, POLL_7E7_CELL_32,
POLL_7E7_CELL_33, POLL_7E7_CELL_34,
POLL_7E7_CELL_35, POLL_7E7_CELL_36,
POLL_7E7_CELL_37, POLL_7E7_CELL_38,
POLL_7E7_CELL_39, POLL_7E7_CELL_40,
POLL_7E7_CELL_41, POLL_7E7_CELL_42,
POLL_7E7_CELL_43, POLL_7E7_CELL_44,
POLL_7E7_CELL_45, POLL_7E7_CELL_46,
POLL_7E7_CELL_47, POLL_7E7_CELL_48,
POLL_7E7_CELL_49, POLL_7E7_CELL_50,
POLL_7E7_CELL_51, POLL_7E7_CELL_52,
POLL_7E7_CELL_53, POLL_7E7_CELL_54,
POLL_7E7_CELL_55, POLL_7E7_CELL_56,
POLL_7E7_CELL_57, POLL_7E7_CELL_58,
POLL_7E7_CELL_59, POLL_7E7_CELL_60,
POLL_7E7_CELL_61, POLL_7E7_CELL_62,
POLL_7E7_CELL_63, POLL_7E7_CELL_64,
POLL_7E7_CELL_65, POLL_7E7_CELL_66,
POLL_7E7_CELL_67, POLL_7E7_CELL_68,
POLL_7E7_CELL_69, POLL_7E7_CELL_70,
POLL_7E7_CELL_71, POLL_7E7_CELL_72,
POLL_7E7_CELL_73, POLL_7E7_CELL_74,
POLL_7E7_CELL_75, POLL_7E7_CELL_76,
POLL_7E7_CELL_77, POLL_7E7_CELL_78,
POLL_7E7_CELL_79, POLL_7E7_CELL_80,
POLL_7E7_CELL_81, POLL_7E7_CELL_82,
POLL_7E7_CELL_83, POLL_7E7_CELL_84,
POLL_7E7_CELL_85, POLL_7E7_CELL_86,
POLL_7E7_CELL_87, POLL_7E7_CELL_88,
POLL_7E7_CELL_89, POLL_7E7_CELL_90,
POLL_7E7_CELL_91, POLL_7E7_CELL_92,
POLL_7E7_CELL_93, POLL_7E7_CELL_94,
POLL_7E7_CELL_95, POLL_7E7_CELL_96};
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
datalayer.battery.status.real_soc = battery_SOC_display;
//datalayer.battery.status.voltage_dV = battery_voltage * 0.52;
datalayer.battery.status.voltage_dV = (battery_voltage_periodic / 8) * 10;
datalayer.battery.status.current_dA = battery_current_7E7;
datalayer.battery.info.total_capacity_Wh;
datalayer.battery.status.remaining_capacity_Wh;
datalayer.battery.status.soh_pptt;
datalayer.battery.status.max_discharge_power_W;
datalayer.battery.status.max_charge_power_W;
// Store temperatures in an array
int16_t temperatures[] = {temperature_1, temperature_2, temperature_3, temperature_4, temperature_5, temperature_6};
// Initialize highest and lowest to the first element
temperature_highest = temperatures[0];
temperature_lowest = temperatures[0];
// Iterate through the array to find the highest and lowest values
for (uint8_t i = 1; i < 6; ++i) {
if (temperatures[i] > temperature_highest) {
temperature_highest = temperatures[i];
}
if (temperatures[i] < temperature_lowest) {
temperature_lowest = temperatures[i];
}
}
datalayer.battery.status.temperature_min_dC = temperature_lowest * 10;
datalayer.battery.status.temperature_max_dC = temperature_highest * 10;
//Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, battery_cell_voltages, 96 * sizeof(uint16_t));
// Update webserver datalayer
datalayer_extended.boltampera.battery_5V_ref = battery_5V_ref;
datalayer_extended.boltampera.battery_module_temp_1 = battery_module_temp_1;
datalayer_extended.boltampera.battery_module_temp_2 = battery_module_temp_2;
datalayer_extended.boltampera.battery_module_temp_3 = battery_module_temp_3;
datalayer_extended.boltampera.battery_module_temp_4 = battery_module_temp_4;
datalayer_extended.boltampera.battery_module_temp_5 = battery_module_temp_5;
datalayer_extended.boltampera.battery_module_temp_6 = battery_module_temp_6;
datalayer_extended.boltampera.battery_cell_average_voltage = battery_cell_average_voltage;
datalayer_extended.boltampera.battery_cell_average_voltage_2 = battery_cell_average_voltage_2;
datalayer_extended.boltampera.battery_terminal_voltage = battery_terminal_voltage;
datalayer_extended.boltampera.battery_ignition_power_mode = battery_ignition_power_mode;
datalayer_extended.boltampera.battery_current_7E7 = battery_current_7E7;
datalayer_extended.boltampera.battery_capacity_my17_18 = battery_capacity_my17_18;
datalayer_extended.boltampera.battery_capacity_my19plus = battery_capacity_my19plus;
datalayer_extended.boltampera.battery_SOC_display = battery_SOC_display;
datalayer_extended.boltampera.battery_SOC_raw_highprec = battery_SOC_raw_highprec;
datalayer_extended.boltampera.battery_max_temperature = battery_max_temperature;
datalayer_extended.boltampera.battery_min_temperature = battery_min_temperature;
datalayer_extended.boltampera.battery_min_cell_voltage = battery_min_cell_voltage;
datalayer_extended.boltampera.battery_max_cell_voltage = battery_max_cell_voltage;
datalayer_extended.boltampera.battery_lowest_cell = battery_lowest_cell;
datalayer_extended.boltampera.battery_highest_cell = battery_highest_cell;
datalayer_extended.boltampera.battery_internal_resistance = battery_internal_resistance;
datalayer_extended.boltampera.battery_voltage_polled = battery_voltage_polled;
datalayer_extended.boltampera.battery_vehicle_isolation = battery_vehicle_isolation;
datalayer_extended.boltampera.battery_isolation_kohm = battery_isolation_kohm;
datalayer_extended.boltampera.battery_HV_locked = battery_HV_locked;
datalayer_extended.boltampera.battery_crash_event = battery_crash_event;
datalayer_extended.boltampera.battery_HVIL = battery_HVIL;
datalayer_extended.boltampera.battery_HVIL_status = battery_HVIL_status;
datalayer_extended.boltampera.battery_current_7E4 = battery_current_7E4;
}
void receive_can_battery(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x200:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
break;
case 0x202:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
break;
case 0x204:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
break;
case 0x206:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
break;
case 0x208:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
mux = ((rx_frame.data.u8[6] & 0xE0) >> 5); //goes from 0-7
break;
case 0x20C:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x216:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x2C7:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_voltage_periodic = (rx_frame.data.u8[3] << 4) | (rx_frame.data.u8[4] >> 4);
break;
case 0x260:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x270:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x272:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x274:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x302:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
temperature_1 = ((rx_frame.data.u8[1] / 2) - 40); //Module 1 Temperature
temperature_2 = ((rx_frame.data.u8[2] / 2) - 40); //Module 2 Temperature
temperature_3 = ((rx_frame.data.u8[3] / 2) - 40); //Module 3 Temperature
temperature_4 = ((rx_frame.data.u8[4] / 2) - 40); //Module 4 Temperature
temperature_5 = ((rx_frame.data.u8[5] / 2) - 40); //Module 5 Temperature
temperature_6 = ((rx_frame.data.u8[6] / 2) - 40); //Module 6 Temperature
break;
case 0x3E3:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x460:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x5EF:
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EC: //When polling 7E4 BMS replies with 7EC ??
if (rx_frame.data.u8[0] == 0x10) { //"PID Header"
transmit_can(&BOLT_ACK_7E4, can_config.battery);
}
//Frame 2 & 3 contains reply
reply_poll_7E4 = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
switch (reply_poll_7E4) {
case POLL_7E4_CAPACITY_EST_GEN1:
battery_capacity_my17_18 = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case POLL_7E4_CAPACITY_EST_GEN2:
battery_capacity_my19plus = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case POLL_7E4_SOC_DISPLAY:
battery_SOC_display = ((rx_frame.data.u8[4] * 100) / 255);
break;
case POLL_7E4_SOC_RAW_HIGHPREC:
battery_SOC_raw_highprec = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 100) / 65535);
break;
case POLL_7E4_MAX_TEMPERATURE:
battery_max_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E4_MIN_TEMPERATURE:
battery_min_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E4_MIN_CELL_V:
battery_min_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 1666;
break;
case POLL_7E4_MAX_CELL_V:
battery_max_cell_voltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 1666;
break;
case POLL_7E4_INTERNAL_RES:
battery_internal_resistance = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 2;
break;
case POLL_7E4_LOWEST_CELL_NUMBER:
battery_lowest_cell = rx_frame.data.u8[4];
break;
case POLL_7E4_HIGHEST_CELL_NUMBER:
battery_highest_cell = rx_frame.data.u8[4];
break;
case POLL_7E4_VOLTAGE:
battery_voltage_polled = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.52);
break;
case POLL_7E4_VEHICLE_ISOLATION:
battery_vehicle_isolation = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case POLL_7E4_ISOLATION_TEST_KOHM:
battery_isolation_kohm = (rx_frame.data.u8[4] * 25);
break;
case POLL_7E4_HV_LOCKED_OUT:
battery_HV_locked = rx_frame.data.u8[4];
break;
case POLL_7E4_CRASH_EVENT:
battery_crash_event = rx_frame.data.u8[4];
break;
case POLL_7E4_HVIL:
battery_HVIL = rx_frame.data.u8[4];
break;
case POLL_7E4_HVIL_STATUS:
battery_HVIL_status = rx_frame.data.u8[4];
break;
case POLL_7E4_CURRENT:
battery_current_7E4 = (((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / (-6.675));
break;
default:
break;
}
break;
case 0x7EF: //When polling 7E7 BMS replies with 7EF
if (rx_frame.data.u8[0] == 0x10) { //"PID Header"
transmit_can(&BOLT_ACK_7E7, can_config.battery);
}
//Frame 2 & 3 contains reply
reply_poll_7E7 = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
switch (reply_poll_7E7) {
case POLL_7E7_CURRENT:
battery_current_7E7 = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_7E7_5V_REF:
battery_5V_ref = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5) / 65535);
break;
case POLL_7E7_MODULE_TEMP_1:
battery_module_temp_1 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_MODULE_TEMP_2:
battery_module_temp_2 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_MODULE_TEMP_3:
battery_module_temp_3 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_MODULE_TEMP_4:
battery_module_temp_4 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_MODULE_TEMP_5:
battery_module_temp_5 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_MODULE_TEMP_6:
battery_module_temp_6 = (rx_frame.data.u8[4] - 40);
break;
case POLL_7E7_CELL_AVG_VOLTAGE:
battery_cell_average_voltage = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_AVG_VOLTAGE_2:
battery_cell_average_voltage_2 = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) / 8000) * 1000);
break;
case POLL_7E7_TERMINAL_VOLTAGE:
battery_terminal_voltage = rx_frame.data.u8[4] * 2;
break;
case POLL_7E7_IGNITION_POWER_MODE:
battery_ignition_power_mode = rx_frame.data.u8[4];
break;
case POLL_7E7_CELL_01:
battery_cell_voltages[0] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_02:
battery_cell_voltages[1] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_03:
battery_cell_voltages[2] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_04:
battery_cell_voltages[3] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_05:
battery_cell_voltages[4] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_06:
battery_cell_voltages[5] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_07:
battery_cell_voltages[6] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_08:
battery_cell_voltages[7] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_09:
battery_cell_voltages[8] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_10:
battery_cell_voltages[9] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_11:
battery_cell_voltages[10] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_12:
battery_cell_voltages[11] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_13:
battery_cell_voltages[12] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_14:
battery_cell_voltages[13] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_15:
battery_cell_voltages[14] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_16:
battery_cell_voltages[15] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_17:
battery_cell_voltages[16] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_18:
battery_cell_voltages[17] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_19:
battery_cell_voltages[18] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_20:
battery_cell_voltages[19] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_21:
battery_cell_voltages[20] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_22:
battery_cell_voltages[21] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_23:
battery_cell_voltages[22] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_24:
battery_cell_voltages[23] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_25:
battery_cell_voltages[24] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_26:
battery_cell_voltages[25] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_27:
battery_cell_voltages[26] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_28:
battery_cell_voltages[27] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_29:
battery_cell_voltages[28] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_30:
battery_cell_voltages[29] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_31:
battery_cell_voltages[30] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_32:
battery_cell_voltages[31] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_33:
battery_cell_voltages[32] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_34:
battery_cell_voltages[33] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_35:
battery_cell_voltages[34] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_36:
battery_cell_voltages[35] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_37:
battery_cell_voltages[36] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_38:
battery_cell_voltages[37] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_39:
battery_cell_voltages[38] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_40:
battery_cell_voltages[39] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_41:
battery_cell_voltages[40] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_42:
battery_cell_voltages[41] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_43:
battery_cell_voltages[42] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_44:
battery_cell_voltages[43] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_45:
battery_cell_voltages[44] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_46:
battery_cell_voltages[45] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_47:
battery_cell_voltages[46] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_48:
battery_cell_voltages[47] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_49:
battery_cell_voltages[48] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_50:
battery_cell_voltages[49] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_51:
battery_cell_voltages[50] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_52:
battery_cell_voltages[51] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_53:
battery_cell_voltages[52] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_54:
battery_cell_voltages[53] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_55:
battery_cell_voltages[54] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_56:
battery_cell_voltages[55] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_57:
battery_cell_voltages[56] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_58:
battery_cell_voltages[57] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_59:
battery_cell_voltages[58] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_60:
battery_cell_voltages[59] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_61:
battery_cell_voltages[60] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_62:
battery_cell_voltages[61] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_63:
battery_cell_voltages[62] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_64:
battery_cell_voltages[63] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_65:
battery_cell_voltages[64] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_66:
battery_cell_voltages[65] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_67:
battery_cell_voltages[66] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_68:
battery_cell_voltages[67] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_69:
battery_cell_voltages[68] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_70:
battery_cell_voltages[69] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_71:
battery_cell_voltages[70] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_72:
battery_cell_voltages[71] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_73:
battery_cell_voltages[72] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_74:
battery_cell_voltages[73] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_75:
battery_cell_voltages[74] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_76:
battery_cell_voltages[75] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_77:
battery_cell_voltages[76] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_78:
battery_cell_voltages[77] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_79:
battery_cell_voltages[78] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_80:
battery_cell_voltages[79] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_81:
battery_cell_voltages[80] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_82:
battery_cell_voltages[81] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_83:
battery_cell_voltages[82] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_84:
battery_cell_voltages[83] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_85:
battery_cell_voltages[84] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_86:
battery_cell_voltages[85] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_87:
battery_cell_voltages[86] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_88:
battery_cell_voltages[87] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_89:
battery_cell_voltages[88] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_90:
battery_cell_voltages[89] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_91:
battery_cell_voltages[90] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_92:
battery_cell_voltages[91] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_93:
battery_cell_voltages[92] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_94:
battery_cell_voltages[93] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_95:
battery_cell_voltages[94] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
case POLL_7E7_CELL_96:
battery_cell_voltages[95] = ((((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 5000) / 65535);
break;
default:
break;
}
default:
break;
}
}
void send_can_battery() {
unsigned long currentMillis = millis();
//Send 20ms message
if (currentMillis - previousMillis20ms >= INTERVAL_20_MS) {
// Check if sending of CAN messages has been delayed too much.
if ((currentMillis - previousMillis20ms >= INTERVAL_20_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis20ms));
} else {
clear_event(EVENT_CAN_OVERRUN);
}
previousMillis20ms = currentMillis;
transmit_can(&BOLT_778, can_config.battery);
}
//Send 100ms message
if (currentMillis - previousMillis100ms >= INTERVAL_100_MS) {
previousMillis100ms = currentMillis;
// Update current poll from the 7E7 array
currentpoll_7E7 = poll_commands_7E7[poll_index_7E7];
poll_index_7E7 = (poll_index_7E7 + 1) % 108;
BOLT_POLL_7E7.data.u8[2] = (uint8_t)((currentpoll_7E7 & 0xFF00) >> 8);
BOLT_POLL_7E7.data.u8[3] = (uint8_t)(currentpoll_7E7 & 0x00FF);
transmit_can(&BOLT_POLL_7E7, can_config.battery);
}
//Send 120ms message
if (currentMillis - previousMillis120ms >= 120) {
previousMillis120ms = currentMillis;
// Update current poll from the 7E4 array
currentpoll_7E4 = poll_commands_7E4[poll_index_7E4];
poll_index_7E4 = (poll_index_7E4 + 1) % 19;
BOLT_POLL_7E4.data.u8[2] = (uint8_t)((currentpoll_7E4 & 0xFF00) >> 8);
BOLT_POLL_7E4.data.u8[3] = (uint8_t)(currentpoll_7E4 & 0x00FF);
transmit_can(&BOLT_POLL_7E4, can_config.battery);
}
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Chevrolet Bolt EV/Opel Ampera-e", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif

View file

@ -0,0 +1,146 @@
#ifndef BOLT_AMPERA_BATTERY_H
#define BOLT_AMPERA_BATTERY_H
#include <Arduino.h>
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4150 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2500
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define POLL_7E4_CAPACITY_EST_GEN1 0x41A3
#define POLL_7E4_CAPACITY_EST_GEN2 0x45F9
#define POLL_7E4_SOC_DISPLAY 0x8334
#define POLL_7E4_SOC_RAW_HIGHPREC 0x43AF
#define POLL_7E4_MAX_TEMPERATURE 0x4349
#define POLL_7E4_MIN_TEMPERATURE 0x434A
#define POLL_7E4_MIN_CELL_V 0x4329
#define POLL_7E4_MAX_CELL_V 0x432B
#define POLL_7E4_INTERNAL_RES 0x40E9
#define POLL_7E4_LOWEST_CELL_NUMBER 0x433B
#define POLL_7E4_HIGHEST_CELL_NUMBER 0x433C
#define POLL_7E4_VOLTAGE 0x432D
#define POLL_7E4_VEHICLE_ISOLATION 0x41EC
#define POLL_7E4_ISOLATION_TEST_KOHM 0x43A6
#define POLL_7E4_HV_LOCKED_OUT 0x44F8
#define POLL_7E4_CRASH_EVENT 0x4522
#define POLL_7E4_HVIL 0x4310
#define POLL_7E4_HVIL_STATUS 0x4311
#define POLL_7E4_CURRENT 0x4356
#define POLL_7E7_CURRENT 0x40D4
#define POLL_7E7_5V_REF 0x40D3
#define POLL_7E7_MODULE_TEMP_1 0x40D7
#define POLL_7E7_MODULE_TEMP_2 0x40D9
#define POLL_7E7_MODULE_TEMP_3 0x40DB
#define POLL_7E7_MODULE_TEMP_4 0x40DD
#define POLL_7E7_MODULE_TEMP_5 0x40DF
#define POLL_7E7_MODULE_TEMP_6 0x40E1
#define POLL_7E7_CELL_AVG_VOLTAGE 0xC218
#define POLL_7E7_CELL_AVG_VOLTAGE_2 0x44B9
#define POLL_7E7_TERMINAL_VOLTAGE 0x82A3
#define POLL_7E7_IGNITION_POWER_MODE 0x8002
#define POLL_7E7_CELL_01 0x4181
#define POLL_7E7_CELL_02 0x4182
#define POLL_7E7_CELL_03 0x4183
#define POLL_7E7_CELL_04 0x4184
#define POLL_7E7_CELL_05 0x4185
#define POLL_7E7_CELL_06 0x4186
#define POLL_7E7_CELL_07 0x4187
#define POLL_7E7_CELL_08 0x4188
#define POLL_7E7_CELL_09 0x4189
#define POLL_7E7_CELL_10 0x418A
#define POLL_7E7_CELL_11 0x418B
#define POLL_7E7_CELL_12 0x418C
#define POLL_7E7_CELL_13 0x418D
#define POLL_7E7_CELL_14 0x418E
#define POLL_7E7_CELL_15 0x418F
#define POLL_7E7_CELL_16 0x4190
#define POLL_7E7_CELL_17 0x4191
#define POLL_7E7_CELL_18 0x4192
#define POLL_7E7_CELL_19 0x4193
#define POLL_7E7_CELL_20 0x4194
#define POLL_7E7_CELL_21 0x4195
#define POLL_7E7_CELL_22 0x4196
#define POLL_7E7_CELL_23 0x4197
#define POLL_7E7_CELL_24 0x4198
#define POLL_7E7_CELL_25 0x4199
#define POLL_7E7_CELL_26 0x419A
#define POLL_7E7_CELL_27 0x419B
#define POLL_7E7_CELL_28 0x419C
#define POLL_7E7_CELL_29 0x419D
#define POLL_7E7_CELL_30 0x419E
#define POLL_7E7_CELL_31 0x419F
#define POLL_7E7_CELL_32 0x4200
#define POLL_7E7_CELL_33 0x4201
#define POLL_7E7_CELL_34 0x4202
#define POLL_7E7_CELL_35 0x4203
#define POLL_7E7_CELL_36 0x4204
#define POLL_7E7_CELL_37 0x4205
#define POLL_7E7_CELL_38 0x4206
#define POLL_7E7_CELL_39 0x4207
#define POLL_7E7_CELL_40 0x4208
#define POLL_7E7_CELL_41 0x4209
#define POLL_7E7_CELL_42 0x420A
#define POLL_7E7_CELL_43 0x420B
#define POLL_7E7_CELL_44 0x420C
#define POLL_7E7_CELL_45 0x420D
#define POLL_7E7_CELL_46 0x420E
#define POLL_7E7_CELL_47 0x420F
#define POLL_7E7_CELL_48 0x4210
#define POLL_7E7_CELL_49 0x4211
#define POLL_7E7_CELL_50 0x4212
#define POLL_7E7_CELL_51 0x4213
#define POLL_7E7_CELL_52 0x4214
#define POLL_7E7_CELL_53 0x4215
#define POLL_7E7_CELL_54 0x4216
#define POLL_7E7_CELL_55 0x4217
#define POLL_7E7_CELL_56 0x4218
#define POLL_7E7_CELL_57 0x4219
#define POLL_7E7_CELL_58 0x421A
#define POLL_7E7_CELL_59 0x421B
#define POLL_7E7_CELL_60 0x421C
#define POLL_7E7_CELL_61 0x421D
#define POLL_7E7_CELL_62 0x421E
#define POLL_7E7_CELL_63 0x421F
#define POLL_7E7_CELL_64 0x4220
#define POLL_7E7_CELL_65 0x4221
#define POLL_7E7_CELL_66 0x4222
#define POLL_7E7_CELL_67 0x4223
#define POLL_7E7_CELL_68 0x4224
#define POLL_7E7_CELL_69 0x4225
#define POLL_7E7_CELL_70 0x4226
#define POLL_7E7_CELL_71 0x4227
#define POLL_7E7_CELL_72 0x4228
#define POLL_7E7_CELL_73 0x4229
#define POLL_7E7_CELL_74 0x422A
#define POLL_7E7_CELL_75 0x422B
#define POLL_7E7_CELL_76 0x422C
#define POLL_7E7_CELL_77 0x422D
#define POLL_7E7_CELL_78 0x422E
#define POLL_7E7_CELL_79 0x422F
#define POLL_7E7_CELL_80 0x4230
#define POLL_7E7_CELL_81 0x4231
#define POLL_7E7_CELL_82 0x4232
#define POLL_7E7_CELL_83 0x4233
#define POLL_7E7_CELL_84 0x4234
#define POLL_7E7_CELL_85 0x4235
#define POLL_7E7_CELL_86 0x4236
#define POLL_7E7_CELL_87 0x4237
#define POLL_7E7_CELL_88 0x4238
#define POLL_7E7_CELL_89 0x4239
#define POLL_7E7_CELL_90 0x423A
#define POLL_7E7_CELL_91 0x423B
#define POLL_7E7_CELL_92 0x423C
#define POLL_7E7_CELL_93 0x423D
#define POLL_7E7_CELL_94 0x423E
#define POLL_7E7_CELL_95 0x423F
#define POLL_7E7_CELL_96 0x4240
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
#endif

View file

@ -1,6 +1,7 @@
#include "../include.h"
#ifdef BYD_ATTO_3_BATTERY
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h"
#include "BYD-ATTO-3-BATTERY.h"
@ -14,18 +15,19 @@
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static bool SOC_method = false;
static uint8_t counter_50ms = 0;
static uint8_t counter_100ms = 0;
static uint8_t frame6_counter = 0xB;
static uint8_t frame7_counter = 0x5;
static int16_t temperature_ambient = 0;
static int16_t daughterboard_temperatures[10];
static int16_t lowest_temperature = 0;
static int16_t highest_temperature = 0;
static int16_t calc_min_temperature = 0;
static int16_t calc_max_temperature = 0;
static uint16_t battery_voltage = 0;
static int16_t battery_temperature_ambient = 0;
static int16_t battery_daughterboard_temperatures[10];
static int16_t battery_lowest_temperature = 0;
static int16_t battery_highest_temperature = 0;
static int16_t battery_calc_min_temperature = 0;
static int16_t battery_calc_max_temperature = 0;
static uint16_t battery_highprecision_SOC = 0;
static uint16_t BMS_SOC = 0;
static uint16_t BMS_voltage = 0;
static int16_t BMS_current = 0;
@ -34,7 +36,23 @@ static int16_t BMS_highest_cell_temperature = 0;
static int16_t BMS_average_cell_temperature = 0;
static uint16_t BMS_lowest_cell_voltage_mV = 3300;
static uint16_t BMS_highest_cell_voltage_mV = 3300;
#ifdef DOUBLE_BATTERY
static int16_t battery2_temperature_ambient = 0;
static int16_t battery2_daughterboard_temperatures[10];
static int16_t battery2_lowest_temperature = 0;
static int16_t battery2_highest_temperature = 0;
static int16_t battery2_calc_min_temperature = 0;
static int16_t battery2_calc_max_temperature = 0;
static uint16_t battery2_highprecision_SOC = 0;
static uint16_t BMS2_SOC = 0;
static uint16_t BMS2_voltage = 0;
static int16_t BMS2_current = 0;
static int16_t BMS2_lowest_cell_temperature = 0;
static int16_t BMS2_highest_cell_temperature = 0;
static int16_t BMS2_average_cell_temperature = 0;
static uint16_t BMS2_lowest_cell_voltage_mV = 3300;
static uint16_t BMS2_highest_cell_voltage_mV = 3300;
#endif //DOUBLE_BATTERY
#define POLL_FOR_BATTERY_SOC 0x05
#define POLL_FOR_BATTERY_VOLTAGE 0x08
#define POLL_FOR_BATTERY_CURRENT 0x09
@ -44,6 +62,8 @@ static uint16_t BMS_highest_cell_voltage_mV = 3300;
#define POLL_FOR_BATTERY_CELL_MV_MAX 0x2D
#define POLL_FOR_BATTERY_CELL_MV_MIN 0x2B
#define UNKNOWN_POLL_1 0xFC
#define ESTIMATED 0
#define MEASURED 1
static uint16_t poll_state = POLL_FOR_BATTERY_SOC;
CAN_frame ATTO_3_12D = {.FD = false,
@ -91,60 +111,69 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.voltage_dV = BMS_voltage * 10;
}
//datalayer.battery.status.real_soc = BMS_SOC * 100; //TODO: This is not yet found!
// We instead estimate the SOC% based on the battery voltage
// This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that!
#ifdef USE_ESTIMATED_SOC
// When the battery is crashed hard, it locks itself and SOC becomes unavailable.
// We instead estimate the SOC% based on the battery voltage.
// This is a bad solution, you wont be able to use 100% of the battery
datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV);
SOC_method = ESTIMATED;
#else // Pack is not crashed, we can use periodically transmitted SOC
datalayer.battery.status.real_soc = battery_highprecision_SOC * 10;
SOC_method = MEASURED;
#endif
datalayer.battery.status.current_dA = -BMS_current;
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.max_discharge_power_W = 10000; //TODO: Map from CAN later on
datalayer.battery.status.max_discharge_power_W = MAXPOWER_DISCHARGE_W; //TODO: Map from CAN later on
datalayer.battery.status.max_charge_power_W = 10000; //TODO: Map from CAN later on
datalayer.battery.status.active_power_W =
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
datalayer.battery.status.max_charge_power_W = MAXPOWER_CHARGE_W; //TODO: Map from CAN later on
datalayer.battery.status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
// Initialize min and max variables
calc_min_temperature = daughterboard_temperatures[0];
calc_max_temperature = daughterboard_temperatures[0];
battery_calc_min_temperature = battery_daughterboard_temperatures[0];
battery_calc_max_temperature = battery_daughterboard_temperatures[0];
// Loop through the array of daughterboard temps to find the smallest and largest values
for (int i = 1; i < 10; i++) {
if (daughterboard_temperatures[i] < calc_min_temperature) {
calc_min_temperature = daughterboard_temperatures[i];
if (battery_daughterboard_temperatures[i] < battery_calc_min_temperature) {
battery_calc_min_temperature = battery_daughterboard_temperatures[i];
}
if (daughterboard_temperatures[i] > calc_max_temperature) {
calc_max_temperature = daughterboard_temperatures[i];
if (battery_daughterboard_temperatures[i] > battery_calc_max_temperature) {
battery_calc_max_temperature = battery_daughterboard_temperatures[i];
}
}
datalayer.battery.status.temperature_min_dC = calc_min_temperature * 10; // Add decimals
datalayer.battery.status.temperature_max_dC = calc_max_temperature * 10;
datalayer.battery.status.temperature_min_dC = battery_calc_min_temperature * 10; // Add decimals
datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10;
#ifdef DEBUG_VIA_USB
#endif
// Update webserver datalayer
datalayer_extended.bydAtto3.SOC_method = SOC_method;
datalayer_extended.bydAtto3.SOC_estimated = datalayer.battery.status.real_soc;
//Once we implement switching logic, remember to change from where the estimated is taken
datalayer_extended.bydAtto3.SOC_highprec = battery_highprecision_SOC;
datalayer_extended.bydAtto3.SOC_polled = BMS_SOC;
datalayer_extended.bydAtto3.voltage_periodic = battery_voltage;
datalayer_extended.bydAtto3.voltage_polled = BMS_voltage;
}
void receive_can_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) { //Log values taken with 422V from battery
case 0x244: //00,00,00,04,41,0F,20,8B - Static, values never changes between logs
break;
case 0x245: //01,00,02,19,3A,25,90,F4 Seems to have a mux in frame0
//02,00,90,01,79,79,90,EA // Point of interest, went from 7E,75 to 7B,7C when discharging
//03,C6,88,12,FD,48,90,5C
//04,00,FF,FF,00,00,90,6D
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//02,00,90,01,79,79,90,EA // Point of interest, went from 7E,75 to 7B,7C when discharging
//03,C6,88,12,FD,48,90,5C
//04,00,FF,FF,00,00,90,6D
if (rx_frame.data.u8[0] == 0x01) {
temperature_ambient = (rx_frame.data.u8[4] - 40); // TODO, check if this is actually temperature_ambient
battery_temperature_ambient =
(rx_frame.data.u8[4] - 40); // TODO, check if this is actually temperature_ambient
}
break;
case 0x286: //01,FF,FF,FF,FF,FF,FF,04 - Static, values never changes between logs
@ -190,18 +219,18 @@ void receive_can_battery(CAN_frame rx_frame) {
break;
case 0x43C: // Daughterboard temperatures reside in this CAN message
if (rx_frame.data.u8[0] == 0x00) {
daughterboard_temperatures[0] = (rx_frame.data.u8[1] - 40);
daughterboard_temperatures[1] = (rx_frame.data.u8[2] - 40);
daughterboard_temperatures[2] = (rx_frame.data.u8[3] - 40);
daughterboard_temperatures[3] = (rx_frame.data.u8[4] - 40);
daughterboard_temperatures[4] = (rx_frame.data.u8[5] - 40);
daughterboard_temperatures[5] = (rx_frame.data.u8[6] - 40);
battery_daughterboard_temperatures[0] = (rx_frame.data.u8[1] - 40);
battery_daughterboard_temperatures[1] = (rx_frame.data.u8[2] - 40);
battery_daughterboard_temperatures[2] = (rx_frame.data.u8[3] - 40);
battery_daughterboard_temperatures[3] = (rx_frame.data.u8[4] - 40);
battery_daughterboard_temperatures[4] = (rx_frame.data.u8[5] - 40);
battery_daughterboard_temperatures[5] = (rx_frame.data.u8[6] - 40);
}
if (rx_frame.data.u8[0] == 0x01) {
daughterboard_temperatures[6] = (rx_frame.data.u8[1] - 40);
daughterboard_temperatures[7] = (rx_frame.data.u8[2] - 40);
daughterboard_temperatures[8] = (rx_frame.data.u8[3] - 40);
daughterboard_temperatures[9] = (rx_frame.data.u8[4] - 40);
battery_daughterboard_temperatures[6] = (rx_frame.data.u8[1] - 40);
battery_daughterboard_temperatures[7] = (rx_frame.data.u8[2] - 40);
battery_daughterboard_temperatures[8] = (rx_frame.data.u8[3] - 40);
battery_daughterboard_temperatures[9] = (rx_frame.data.u8[4] - 40);
}
break;
case 0x43D: //Varies a lot
@ -209,16 +238,20 @@ void receive_can_battery(CAN_frame rx_frame) {
case 0x444: //9E,01,88,13,64,64,98,65
//9A,01,B6,13,64,64,98,3B //407.5V 18deg
//9B,01,B8,13,64,64,98,38 //408.5V 14deg
//lowprecision_SOC = ???
battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0];
//battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7
break;
case 0x445: //00,98,FF,FF,63,20,4E,98 - Static, values never changes between logs
break;
case 0x446: //2C,D4,0C,4D,21,DC,0C,9D - 0,1,7th frame varies a lot
break;
case 0x447: // Seems to contain more temperatures, highest and lowest?
//06,38,01,3B,E0,03,39,69
//06,36,02,36,E0,03,36,72,
lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now
highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now
case 0x447: // Seems to contain more temperatures, highest and lowest?
//06,38,01,3B,E0,03,39,69
//06,36,02,36,E0,03,36,72,
battery_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]; // 03 E0 = 992 = 99.2%
battery_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now
battery_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now
break;
case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs
break;
@ -302,6 +335,9 @@ void send_can_battery() {
ATTO_3_12D.data.u8[7] = (0x09 | (frame7_counter << 4));
transmit_can(&ATTO_3_12D, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&ATTO_3_12D, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
@ -320,6 +356,9 @@ void send_can_battery() {
}
transmit_can(&ATTO_3_441, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&ATTO_3_441, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
@ -364,16 +403,207 @@ void send_can_battery() {
}
transmit_can(&ATTO_3_7E7_POLL, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&ATTO_3_7E7_POLL, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("BYD Atto 3 battery selected");
#endif
datalayer.battery.info.max_design_voltage_dV = 4410; // Over this charging is not possible
datalayer.battery.info.min_design_voltage_dV = 3800; // Under this discharging is disabled
strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 126;
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
//Due to the Datalayer having 370.0V as startup value, which is 10V lower than the Atto 3 min voltage 380.0V
//We now init the value to 380.1V to avoid false positive events.
datalayer.battery.status.voltage_dV = MIN_PACK_VOLTAGE_DV + 1;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = 126;
datalayer.battery2.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery2.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery2.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery2.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery2.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
#endif //DOUBLE_BATTERY
}
#ifdef DOUBLE_BATTERY
void update_values_battery2() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
if (BMS2_voltage > 0) {
datalayer.battery2.status.voltage_dV = BMS2_voltage * 10;
}
//datalayer.battery2.status.real_soc = BMS_SOC * 100; //TODO: This is not yet found!
// We instead estimate the SOC% based on the battery2 voltage
// This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that!
datalayer.battery2.status.real_soc = estimateSOC(datalayer.battery2.status.voltage_dV);
datalayer.battery2.status.current_dA = -BMS2_current;
datalayer.battery2.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh);
datalayer.battery2.status.max_discharge_power_W = 10000; //TODO: Map from CAN later on
datalayer.battery2.status.max_charge_power_W = 10000; //TODO: Map from CAN later on
datalayer.battery2.status.cell_max_voltage_mV = BMS2_highest_cell_voltage_mV;
datalayer.battery2.status.cell_min_voltage_mV = BMS2_lowest_cell_voltage_mV;
// Initialize min and max variables
battery2_calc_min_temperature = battery2_daughterboard_temperatures[0];
battery2_calc_max_temperature = battery2_daughterboard_temperatures[0];
// Loop through the array of daughterboard temps to find the smallest and largest values
for (int i = 1; i < 10; i++) {
if (battery2_daughterboard_temperatures[i] < battery2_calc_min_temperature) {
battery2_calc_min_temperature = battery2_daughterboard_temperatures[i];
}
if (battery2_daughterboard_temperatures[i] > battery2_calc_max_temperature) {
battery2_calc_max_temperature = battery2_daughterboard_temperatures[i];
}
}
datalayer.battery2.status.temperature_min_dC = battery2_calc_min_temperature * 10; // Add decimals
datalayer.battery2.status.temperature_max_dC = battery2_calc_max_temperature * 10;
}
void receive_can_battery2(CAN_frame rx_frame) {
switch (rx_frame.ID) { //Log values taken with 422V from battery2
case 0x244: //00,00,00,04,41,0F,20,8B - Static, values never changes between logs
break;
case 0x245: //01,00,02,19,3A,25,90,F4 Seems to have a mux in frame0
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//02,00,90,01,79,79,90,EA // Point of interest, went from 7E,75 to 7B,7C when discharging
//03,C6,88,12,FD,48,90,5C
//04,00,FF,FF,00,00,90,6D
if (rx_frame.data.u8[0] == 0x01) {
battery2_temperature_ambient =
(rx_frame.data.u8[4] - 40); // TODO, check if this is actually temperature_ambient
}
break;
case 0x286: //01,FF,FF,FF,FF,FF,FF,04 - Static, values never changes between logs
break;
case 0x334: //FF,FF,FF,FC,3F,00,F0,D7 - Static, values never changes between logs
break;
case 0x338: //01,52,02,00,88,13,00,0F
//01,51,02,00,88,13,00,10 407.5V 18deg
//01,4F,02,00,88,13,00,12 408.5V 14deg
break;
case 0x344: //00,52,02,CC,1F,FF,04,BD
break;
case 0x345: //27,0B,00,00,00,E0,01,EC - Static, values never changes between logs
break;
case 0x347: //FF,00,00,F9,FF,FF,FF,0A - Static, values never changes between logs
break;
case 0x34A: //00,52,02,CC,1F,FF,04,BD
//00,51,02,CC,1F,FF,04,BE //407.5V 18deg
//00,4F,02,CC,1F,FF,04,C0 //408.5V 14deg
break;
case 0x35E: //01,00,C8,32,00,63,00,A1 - Flickering between A0 and A1, Could be temperature?
//01,00,64,01,10,63,00,26 //407.5V 18deg
//01,00,64,1C,10,63,00,0B //408.5V 14deg
break;
case 0x360: //30,19,DE,D1,0B,C3,4B,EE - Static, values never changes between logs, Last and first byte has F-0 counters
break;
case 0x36C: //01,57,13,DC,08,70,17,29 Seems to have a mux in frame0 , first message is static, never changes between logs
//02,03,DC,05,C0,0F,0F,3B - Static, values never changes between logs
//03,86,01,40,06,5C,02,D1 - Static, values never changes between logs
//04,57,13,73,04,01,FF,1A - Static, values never changes between logs
//05,FF,FF,FF,FF,FF,FF,00 - Static, values never changes between logs
break;
case 0x438: //55,55,01,F6,47,2E,10,D9 - 0x10D9 = 4313
//55,55,01,F6,47,FD,0F,0B //407.5V 18deg
//55,55,01,F6,47,15,10,F2 //408.5V 14deg
break;
case 0x43A: //7E,0A,B0,1C,63,E1,03,64
//7E,0A,E0,1E,63,E1,03,32 //407.5V 18deg
//7E,0A,66,1C,63,E1,03,AE //408.5V 14deg
break;
case 0x43B: //01,3B,06,39,FF,64,64,BD
//01,3B,06,38,FF,64,64,BE
break;
case 0x43C: // Daughterboard temperatures reside in this CAN message
if (rx_frame.data.u8[0] == 0x00) {
battery2_daughterboard_temperatures[0] = (rx_frame.data.u8[1] - 40);
battery2_daughterboard_temperatures[1] = (rx_frame.data.u8[2] - 40);
battery2_daughterboard_temperatures[2] = (rx_frame.data.u8[3] - 40);
battery2_daughterboard_temperatures[3] = (rx_frame.data.u8[4] - 40);
battery2_daughterboard_temperatures[4] = (rx_frame.data.u8[5] - 40);
battery2_daughterboard_temperatures[5] = (rx_frame.data.u8[6] - 40);
}
if (rx_frame.data.u8[0] == 0x01) {
battery2_daughterboard_temperatures[6] = (rx_frame.data.u8[1] - 40);
battery2_daughterboard_temperatures[7] = (rx_frame.data.u8[2] - 40);
battery2_daughterboard_temperatures[8] = (rx_frame.data.u8[3] - 40);
battery2_daughterboard_temperatures[9] = (rx_frame.data.u8[4] - 40);
}
break;
case 0x43D: //Varies a lot
break;
case 0x444: //9E,01,88,13,64,64,98,65
//9A,01,B6,13,64,64,98,3B //407.5V 18deg
//9B,01,B8,13,64,64,98,38 //408.5V 14deg
break;
case 0x445: //00,98,FF,FF,63,20,4E,98 - Static, values never changes between logs
break;
case 0x446: //2C,D4,0C,4D,21,DC,0C,9D - 0,1,7th frame varies a lot
break;
case 0x447: // Seems to contain more temperatures, highest and lowest?
//06,38,01,3B,E0,03,39,69
//06,36,02,36,E0,03,36,72,
battery2_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]; // 03 E0 = 992 = 99.2%
battery2_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now
battery2_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now
break;
case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs
break;
case 0x524: //24,40,00,00,00,00,00,9B - Static, values never changes between logs
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE; // Let system know battery2 is sending CAN
break;
case 0x7EF: //OBD2 PID reply from battery2
switch (rx_frame.data.u8[3]) {
case POLL_FOR_BATTERY_SOC:
BMS2_SOC = rx_frame.data.u8[4];
break;
case POLL_FOR_BATTERY_VOLTAGE:
BMS2_voltage = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case POLL_FOR_BATTERY_CURRENT:
BMS2_current = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) - 5000;
break;
case POLL_FOR_LOWEST_TEMP_CELL:
BMS2_lowest_cell_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_FOR_HIGHEST_TEMP_CELL:
BMS2_highest_cell_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_FOR_BATTERY_PACK_AVG_TEMP:
BMS2_average_cell_temperature = (rx_frame.data.u8[4] - 40);
break;
case POLL_FOR_BATTERY_CELL_MV_MAX:
BMS2_highest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
case POLL_FOR_BATTERY_CELL_MV_MIN:
BMS2_lowest_cell_voltage_mV = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4];
break;
default: //Unrecognized reply
break;
}
break;
default:
break;
}
}
#endif //DOUBLE_BATTERY
#endif

View file

@ -1,10 +1,20 @@
#ifndef ATTO_3_BATTERY_H
#define ATTO_3_BATTERY_H
#include <Arduino.h>
#include "../include.h"
#define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \
// Uncomment this only if you know your BMS is unlocked and able to send SOC%
#define MAXPOWER_CHARGE_W 10000
#define MAXPOWER_DISCHARGE_W 10000
/* Do not modify the rows below */
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4410 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3800
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 3800 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2800 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);

View file

@ -0,0 +1,345 @@
#include "../include.h"
#ifdef CELLPOWER_BMS
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
#include "../devboard/utils/events.h"
#include "CELLPOWER-BMS.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was sent
//Actual content messages
// Optional add-on charger module. Might not be needed to send these towards the BMS to keep it happy.
CAN_frame CELLPOWER_18FF50E9 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E9,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E8 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E8,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E7 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E7,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E5 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E5,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
static bool system_state_discharge = false;
static bool system_state_charge = false;
static bool system_state_cellbalancing = false;
static bool system_state_tricklecharge = false;
static bool system_state_idle = false;
static bool system_state_chargecompleted = false;
static bool system_state_maintenancecharge = false;
static bool IO_state_main_positive_relay = false;
static bool IO_state_main_negative_relay = false;
static bool IO_state_charge_enable = false;
static bool IO_state_precharge_relay = false;
static bool IO_state_discharge_enable = false;
static bool IO_state_IO_6 = false;
static bool IO_state_IO_7 = false;
static bool IO_state_IO_8 = false;
static bool error_Cell_overvoltage = false;
static bool error_Cell_undervoltage = false;
static bool error_Cell_end_of_life_voltage = false;
static bool error_Cell_voltage_misread = false;
static bool error_Cell_over_temperature = false;
static bool error_Cell_under_temperature = false;
static bool error_Cell_unmanaged = false;
static bool error_LMU_over_temperature = false;
static bool error_LMU_under_temperature = false;
static bool error_Temp_sensor_open_circuit = false;
static bool error_Temp_sensor_short_circuit = false;
static bool error_SUB_communication = false;
static bool error_LMU_communication = false;
static bool error_Over_current_IN = false;
static bool error_Over_current_OUT = false;
static bool error_Short_circuit = false;
static bool error_Leak_detected = false;
static bool error_Leak_detection_failed = false;
static bool error_Voltage_difference = false;
static bool error_BMCU_supply_over_voltage = false;
static bool error_BMCU_supply_under_voltage = false;
static bool error_Main_positive_contactor = false;
static bool error_Main_negative_contactor = false;
static bool error_Precharge_contactor = false;
static bool error_Midpack_contactor = false;
static bool error_Precharge_timeout = false;
static bool error_Emergency_connector_override = false;
static bool warning_High_cell_voltage = false;
static bool warning_Low_cell_voltage = false;
static bool warning_High_cell_temperature = false;
static bool warning_Low_cell_temperature = false;
static bool warning_High_LMU_temperature = false;
static bool warning_Low_LMU_temperature = false;
static bool warning_SUB_communication_interfered = false;
static bool warning_LMU_communication_interfered = false;
static bool warning_High_current_IN = false;
static bool warning_High_current_OUT = false;
static bool warning_Pack_resistance_difference = false;
static bool warning_High_pack_resistance = false;
static bool warning_Cell_resistance_difference = false;
static bool warning_High_cell_resistance = false;
static bool warning_High_BMCU_supply_voltage = false;
static bool warning_Low_BMCU_supply_voltage = false;
static bool warning_Low_SOC = false;
static bool warning_Balancing_required_OCV_model = false;
static bool warning_Charger_not_responding = false;
static uint16_t cell_voltage_max_mV = 3700;
static uint16_t cell_voltage_min_mV = 3700;
static int8_t pack_temperature_high_C = 0;
static int8_t pack_temperature_low_C = 0;
static uint16_t battery_pack_voltage_dV = 3700;
static int16_t battery_pack_current_dA = 0;
static uint8_t battery_SOH_percentage = 99;
static uint8_t battery_SOC_percentage = 50;
static uint16_t battery_remaining_dAh = 0;
static uint8_t cell_with_highest_voltage = 0;
static uint8_t cell_with_lowest_voltage = 0;
static uint16_t requested_charge_current_dA = 0;
static uint16_t average_charge_current_dA = 0;
static uint16_t actual_charge_current_dA = 0;
static bool requested_exceeding_average_current = 0;
static bool error_state = false;
void update_values_battery() {
/* Update values from CAN */
datalayer.battery.status.real_soc = battery_SOC_percentage * 100;
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.soh_pptt = battery_SOH_percentage * 100;
datalayer.battery.status.voltage_dV = battery_pack_voltage_dV;
datalayer.battery.status.current_dA = battery_pack_current_dA;
datalayer.battery.status.max_charge_power_W = 5000; //TODO, is this available via CAN?
datalayer.battery.status.max_discharge_power_W = 5000; //TODO, is this available via CAN?
datalayer.battery.status.temperature_min_dC = (int16_t)(pack_temperature_low_C * 10);
datalayer.battery.status.temperature_max_dC = (int16_t)(pack_temperature_high_C * 10);
datalayer.battery.status.cell_max_voltage_mV = cell_voltage_max_mV;
datalayer.battery.status.cell_min_voltage_mV = cell_voltage_min_mV;
/* Update webserver datalayer */
datalayer_extended.cellpower.system_state_discharge = system_state_discharge;
datalayer_extended.cellpower.system_state_charge = system_state_charge;
datalayer_extended.cellpower.system_state_cellbalancing = system_state_cellbalancing;
datalayer_extended.cellpower.system_state_tricklecharge = system_state_tricklecharge;
datalayer_extended.cellpower.system_state_idle = system_state_idle;
datalayer_extended.cellpower.system_state_chargecompleted = system_state_chargecompleted;
datalayer_extended.cellpower.system_state_maintenancecharge = system_state_maintenancecharge;
datalayer_extended.cellpower.IO_state_main_positive_relay = IO_state_main_positive_relay;
datalayer_extended.cellpower.IO_state_main_negative_relay = IO_state_main_negative_relay;
datalayer_extended.cellpower.IO_state_charge_enable = IO_state_charge_enable;
datalayer_extended.cellpower.IO_state_precharge_relay = IO_state_precharge_relay;
datalayer_extended.cellpower.IO_state_discharge_enable = IO_state_discharge_enable;
datalayer_extended.cellpower.IO_state_IO_6 = IO_state_IO_6;
datalayer_extended.cellpower.IO_state_IO_7 = IO_state_IO_7;
datalayer_extended.cellpower.IO_state_IO_8 = IO_state_IO_8;
datalayer_extended.cellpower.error_Cell_overvoltage = error_Cell_overvoltage;
datalayer_extended.cellpower.error_Cell_undervoltage = error_Cell_undervoltage;
datalayer_extended.cellpower.error_Cell_end_of_life_voltage = error_Cell_end_of_life_voltage;
datalayer_extended.cellpower.error_Cell_voltage_misread = error_Cell_voltage_misread;
datalayer_extended.cellpower.error_Cell_over_temperature = error_Cell_over_temperature;
datalayer_extended.cellpower.error_Cell_under_temperature = error_Cell_under_temperature;
datalayer_extended.cellpower.error_Cell_unmanaged = error_Cell_unmanaged;
datalayer_extended.cellpower.error_LMU_over_temperature = error_LMU_over_temperature;
datalayer_extended.cellpower.error_LMU_under_temperature = error_LMU_under_temperature;
datalayer_extended.cellpower.error_Temp_sensor_open_circuit = error_Temp_sensor_open_circuit;
datalayer_extended.cellpower.error_Temp_sensor_short_circuit = error_Temp_sensor_short_circuit;
datalayer_extended.cellpower.error_SUB_communication = error_SUB_communication;
datalayer_extended.cellpower.error_LMU_communication = error_LMU_communication;
datalayer_extended.cellpower.error_Over_current_IN = error_Over_current_IN;
datalayer_extended.cellpower.error_Over_current_OUT = error_Over_current_OUT;
datalayer_extended.cellpower.error_Short_circuit = error_Short_circuit;
datalayer_extended.cellpower.error_Leak_detected = error_Leak_detected;
datalayer_extended.cellpower.error_Leak_detection_failed = error_Leak_detection_failed;
datalayer_extended.cellpower.error_Voltage_difference = error_Voltage_difference;
datalayer_extended.cellpower.error_BMCU_supply_over_voltage = error_BMCU_supply_over_voltage;
datalayer_extended.cellpower.error_BMCU_supply_under_voltage = error_BMCU_supply_under_voltage;
datalayer_extended.cellpower.error_Main_positive_contactor = error_Main_positive_contactor;
datalayer_extended.cellpower.error_Main_negative_contactor = error_Main_negative_contactor;
datalayer_extended.cellpower.error_Precharge_contactor = error_Precharge_contactor;
datalayer_extended.cellpower.error_Midpack_contactor = error_Midpack_contactor;
datalayer_extended.cellpower.error_Precharge_timeout = error_Precharge_timeout;
datalayer_extended.cellpower.error_Emergency_connector_override = error_Emergency_connector_override;
datalayer_extended.cellpower.warning_High_cell_voltage = warning_High_cell_voltage;
datalayer_extended.cellpower.warning_Low_cell_voltage = warning_Low_cell_voltage;
datalayer_extended.cellpower.warning_High_cell_temperature = warning_High_cell_temperature;
datalayer_extended.cellpower.warning_Low_cell_temperature = warning_Low_cell_temperature;
datalayer_extended.cellpower.warning_High_LMU_temperature = warning_High_LMU_temperature;
datalayer_extended.cellpower.warning_Low_LMU_temperature = warning_Low_LMU_temperature;
datalayer_extended.cellpower.warning_SUB_communication_interfered = warning_SUB_communication_interfered;
datalayer_extended.cellpower.warning_LMU_communication_interfered = warning_LMU_communication_interfered;
datalayer_extended.cellpower.warning_High_current_IN = warning_High_current_IN;
datalayer_extended.cellpower.warning_High_current_OUT = warning_High_current_OUT;
datalayer_extended.cellpower.warning_Pack_resistance_difference = warning_Pack_resistance_difference;
datalayer_extended.cellpower.warning_High_pack_resistance = warning_High_pack_resistance;
datalayer_extended.cellpower.warning_Cell_resistance_difference = warning_Cell_resistance_difference;
datalayer_extended.cellpower.warning_High_cell_resistance = warning_High_cell_resistance;
datalayer_extended.cellpower.warning_High_BMCU_supply_voltage = warning_High_BMCU_supply_voltage;
datalayer_extended.cellpower.warning_Low_BMCU_supply_voltage = warning_Low_BMCU_supply_voltage;
datalayer_extended.cellpower.warning_Low_SOC = warning_Low_SOC;
datalayer_extended.cellpower.warning_Balancing_required_OCV_model = warning_Balancing_required_OCV_model;
datalayer_extended.cellpower.warning_Charger_not_responding = warning_Charger_not_responding;
/* Peform safety checks */
if (system_state_chargecompleted) {
//TODO, shall we set max_charge_power_W to 0 incase this is true?
}
if (IO_state_charge_enable) {
//TODO, shall we react on this?
}
if (IO_state_discharge_enable) {
//TODO, shall we react on this?
}
if (error_state) {
//TODO, shall we react on this?
}
}
void receive_can_battery(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x1A4: //PDO1_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
cell_voltage_max_mV = (uint16_t)((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
cell_voltage_min_mV = (uint16_t)((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
pack_temperature_high_C = (int8_t)rx_frame.data.u8[4];
pack_temperature_low_C = (int8_t)rx_frame.data.u8[5];
system_state_discharge = (rx_frame.data.u8[6] & 0x01);
system_state_charge = ((rx_frame.data.u8[6] & 0x02) >> 1);
system_state_cellbalancing = ((rx_frame.data.u8[6] & 0x04) >> 2);
system_state_tricklecharge = ((rx_frame.data.u8[6] & 0x08) >> 3);
system_state_idle = ((rx_frame.data.u8[6] & 0x10) >> 4);
system_state_chargecompleted = ((rx_frame.data.u8[6] & 0x20) >> 5);
system_state_maintenancecharge = ((rx_frame.data.u8[6] & 0x40) >> 6);
IO_state_main_positive_relay = (rx_frame.data.u8[7] & 0x01);
IO_state_main_negative_relay = ((rx_frame.data.u8[7] & 0x02) >> 1);
IO_state_charge_enable = ((rx_frame.data.u8[7] & 0x04) >> 2);
IO_state_precharge_relay = ((rx_frame.data.u8[7] & 0x08) >> 3);
IO_state_discharge_enable = ((rx_frame.data.u8[7] & 0x10) >> 4);
IO_state_IO_6 = ((rx_frame.data.u8[7] & 0x20) >> 5);
IO_state_IO_7 = ((rx_frame.data.u8[7] & 0x40) >> 6);
IO_state_IO_8 = ((rx_frame.data.u8[7] & 0x80) >> 7);
break;
case 0x2A4: //PDO2_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_pack_voltage_dV = (uint16_t)((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery_pack_current_dA = (int16_t)((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
battery_SOH_percentage = (uint8_t)rx_frame.data.u8[4];
battery_SOC_percentage = (uint8_t)rx_frame.data.u8[5];
battery_remaining_dAh = (uint16_t)((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]);
break;
case 0x3A4: //PDO3_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
cell_with_highest_voltage = (uint8_t)rx_frame.data.u8[0];
cell_with_lowest_voltage = (uint8_t)rx_frame.data.u8[1];
break;
case 0x4A4: //PDO4_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
error_Cell_overvoltage = (rx_frame.data.u8[0] & 0x01);
error_Cell_undervoltage = ((rx_frame.data.u8[0] & 0x02) >> 1);
error_Cell_end_of_life_voltage = ((rx_frame.data.u8[0] & 0x04) >> 2);
error_Cell_voltage_misread = ((rx_frame.data.u8[0] & 0x08) >> 3);
error_Cell_over_temperature = ((rx_frame.data.u8[0] & 0x10) >> 4);
error_Cell_under_temperature = ((rx_frame.data.u8[0] & 0x20) >> 5);
error_Cell_unmanaged = ((rx_frame.data.u8[0] & 0x40) >> 6);
error_LMU_over_temperature = ((rx_frame.data.u8[0] & 0x80) >> 7);
error_LMU_under_temperature = (rx_frame.data.u8[1] & 0x01);
error_Temp_sensor_open_circuit = ((rx_frame.data.u8[1] & 0x02) >> 1);
error_Temp_sensor_short_circuit = ((rx_frame.data.u8[1] & 0x04) >> 2);
error_SUB_communication = ((rx_frame.data.u8[1] & 0x08) >> 3);
error_LMU_communication = ((rx_frame.data.u8[1] & 0x10) >> 4);
error_Over_current_IN = ((rx_frame.data.u8[1] & 0x20) >> 5);
error_Over_current_OUT = ((rx_frame.data.u8[1] & 0x40) >> 6);
error_Short_circuit = ((rx_frame.data.u8[1] & 0x80) >> 7);
error_Leak_detected = (rx_frame.data.u8[2] & 0x01);
error_Leak_detection_failed = ((rx_frame.data.u8[2] & 0x02) >> 1);
error_Voltage_difference = ((rx_frame.data.u8[2] & 0x04) >> 2);
error_BMCU_supply_over_voltage = ((rx_frame.data.u8[2] & 0x08) >> 3);
error_BMCU_supply_under_voltage = ((rx_frame.data.u8[2] & 0x10) >> 4);
error_Main_positive_contactor = ((rx_frame.data.u8[2] & 0x20) >> 5);
error_Main_negative_contactor = ((rx_frame.data.u8[2] & 0x40) >> 6);
error_Precharge_contactor = ((rx_frame.data.u8[2] & 0x80) >> 7);
error_Midpack_contactor = (rx_frame.data.u8[3] & 0x01);
error_Precharge_timeout = ((rx_frame.data.u8[3] & 0x02) >> 1);
error_Emergency_connector_override = ((rx_frame.data.u8[3] & 0x04) >> 2);
warning_High_cell_voltage = (rx_frame.data.u8[4] & 0x01);
warning_Low_cell_voltage = ((rx_frame.data.u8[4] & 0x02) >> 1);
warning_High_cell_temperature = ((rx_frame.data.u8[4] & 0x04) >> 2);
warning_Low_cell_temperature = ((rx_frame.data.u8[4] & 0x08) >> 3);
warning_High_LMU_temperature = ((rx_frame.data.u8[4] & 0x10) >> 4);
warning_Low_LMU_temperature = ((rx_frame.data.u8[4] & 0x20) >> 5);
warning_SUB_communication_interfered = ((rx_frame.data.u8[4] & 0x40) >> 6);
warning_LMU_communication_interfered = ((rx_frame.data.u8[4] & 0x80) >> 7);
warning_High_current_IN = (rx_frame.data.u8[5] & 0x01);
warning_High_current_OUT = ((rx_frame.data.u8[5] & 0x02) >> 1);
warning_Pack_resistance_difference = ((rx_frame.data.u8[5] & 0x04) >> 2);
warning_High_pack_resistance = ((rx_frame.data.u8[5] & 0x08) >> 3);
warning_Cell_resistance_difference = ((rx_frame.data.u8[5] & 0x10) >> 4);
warning_High_cell_resistance = ((rx_frame.data.u8[5] & 0x20) >> 5);
warning_High_BMCU_supply_voltage = ((rx_frame.data.u8[5] & 0x40) >> 6);
warning_Low_BMCU_supply_voltage = ((rx_frame.data.u8[5] & 0x80) >> 7);
warning_Low_SOC = (rx_frame.data.u8[6] & 0x01);
warning_Balancing_required_OCV_model = ((rx_frame.data.u8[6] & 0x02) >> 1);
warning_Charger_not_responding = ((rx_frame.data.u8[6] & 0x04) >> 2);
break;
case 0x7A4: //PDO7_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
requested_charge_current_dA = (uint16_t)((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
average_charge_current_dA = (uint16_t)((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
actual_charge_current_dA = (uint16_t)((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
requested_exceeding_average_current = (rx_frame.data.u8[6] & 0x01);
break;
case 0x7A5: //PDO7.1_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
error_state = (rx_frame.data.u8[0] & 0x01);
break;
default:
break;
}
}
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 1s CAN Message
if (currentMillis - previousMillis1s >= INTERVAL_1_S) {
previousMillis1s = currentMillis;
/*
transmit_can(&CELLPOWER_18FF50E9, can_config.battery);
transmit_can(&CELLPOWER_18FF50E8, can_config.battery);
transmit_can(&CELLPOWER_18FF50E7, can_config.battery);
transmit_can(&CELLPOWER_18FF50E5, can_config.battery);
*/
}
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Cellpower BMS", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
#endif // CELLPOWER_BMS

View file

@ -0,0 +1,19 @@
#ifndef CELLPOWER_BMS_H
#define CELLPOWER_BMS_H
#include <Arduino.h>
#include "../include.h"
/* Tweak these according to your battery build */
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 1500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
/* Do not modify any rows below*/
#define BATTERY_SELECTED
#define NATIVECAN_250KBPS
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
#endif

View file

@ -120,7 +120,7 @@ void update_values_battery() {
datalayer.battery.status.voltage_dV = get_measured_voltage() * 10;
datalayer.battery.info.total_capacity_Wh =
((x101_chg_est.RatedBatteryCapacity / 0.11) *
((x101_chg_est.RatedBatteryCapacity / 0.1) *
1000); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
/* TODO max charging rate =
@ -151,8 +151,8 @@ void update_values_battery() {
inline void process_vehicle_charging_minimums(CAN_frame rx_frame) {
x100_chg_lim.MinimumChargeCurrent = rx_frame.data.u8[0];
x100_chg_lim.MinimumBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
x100_chg_lim.MinimumBatteryVoltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
x100_chg_lim.ConstantOfChargingRateIndication = rx_frame.data.u8[6];
}
@ -160,15 +160,14 @@ inline void process_vehicle_charging_maximums(CAN_frame rx_frame) {
x101_chg_est.MaxChargingTime10sBit = rx_frame.data.u8[1];
x101_chg_est.MaxChargingTime1minBit = rx_frame.data.u8[2];
x101_chg_est.EstimatedChargingTime = rx_frame.data.u8[3];
x101_chg_est.RatedBatteryCapacity = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
x101_chg_est.RatedBatteryCapacity = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[5]);
}
inline void process_vehicle_charging_session(CAN_frame rx_frame) {
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
uint16_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
uint8_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
uint16_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
uint8_t newChargingCurrentRequest = rx_frame.data.u8[3];
uint8_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
vehicle_can_initialized = true;
@ -187,6 +186,7 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
x102_chg_session.s.status.StatusChargingError = bitRead(rx_frame.data.u8[5], 2);
x102_chg_session.s.status.StatusVehicle = bitRead(rx_frame.data.u8[5], 3);
x102_chg_session.s.status.StatusNormalStopRequest = bitRead(rx_frame.data.u8[5], 4);
x102_chg_session.s.status.StatusVehicleDischargeCompatible = bitRead(rx_frame.data.u8[5], 7);
x102_chg_session.StateOfCharge = rx_frame.data.u8[6];
@ -197,13 +197,13 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
x102_chg_session.ChargingCurrentRequest = newChargingCurrentRequest;
x102_chg_session.TargetBatteryVoltage = newTargetBatteryVoltage;
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
//Note on p131
uint8_t chargingrate = 0;
if (x100_chg_lim.ConstantOfChargingRateIndication > 0) {
chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
Serial.print("Charge Rate (kW):");
Serial.println(chargingrate);
logging.print("Charge Rate (kW): ");
logging.println(chargingrate);
}
#endif
@ -217,40 +217,40 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
*/
if ((CHADEMO_Status == CHADEMO_INIT && vehicle_permission) ||
(x102_chg_session.s.status.StatusVehicleChargingEnabled && !vehicle_permission)) {
#ifdef DEBUG_VIA_USB
Serial.println("Inconsistent charge/discharge state.");
#ifdef DEBUG_LOG
logging.println("Inconsistent charge/discharge state.");
#endif
CHADEMO_Status = CHADEMO_FAULT;
return;
}
if (x102_chg_session.f.fault.FaultBatteryOverVoltage) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery over voltage.");
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery over voltage.");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (x102_chg_session.f.fault.FaultBatteryUnderVoltage) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery under voltage.");
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery under voltage.");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (x102_chg_session.f.fault.FaultBatteryCurrentDeviation) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery current deviation. Possible EVSE issue?");
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery current deviation. Possible EVSE issue?");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
}
if (x102_chg_session.f.fault.FaultBatteryVoltageDeviation) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle indicates fault, battery voltage deviation. Possible EVSE issue?");
#ifdef DEBUG_LOG
logging.println("Vehicle indicates fault, battery voltage deviation. Possible EVSE issue?");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
@ -264,8 +264,8 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
//FIXME condition nesting or more stanzas needed here for clear determination of cessation reason
if (CHADEMO_Status == CHADEMO_POWERFLOW && EVSE_mode == CHADEMO_CHARGE && !vehicle_permission) {
#ifdef DEBUG_VIA_USB
Serial.println("State of charge ceiling reached or charging interrupted, stop charging");
#ifdef DEBUG_LOG
logging.println("State of charge ceiling reached or charging interrupted, stop charging");
#endif
CHADEMO_Status = CHADEMO_STOP;
return;
@ -273,8 +273,8 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
if (vehicle_permission && CHADEMO_Status == CHADEMO_NEGOTIATE) {
CHADEMO_Status = CHADEMO_EV_ALLOWED;
#ifdef DEBUG_VIA_USB
Serial.println("STATE shift to CHADEMO_EV_ALLOWED in process_vehicle_charging_session()");
#ifdef DEBUG_LOG
logging.println("STATE shift to CHADEMO_EV_ALLOWED in process_vehicle_charging_session()");
#endif
return;
}
@ -284,22 +284,22 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
// consider relocating
if (vehicle_permission && CHADEMO_Status == CHADEMO_EVSE_PREPARE && priorTargetBatteryVoltage == 0 &&
newTargetBatteryVoltage > 0 && x102_chg_session.s.status.StatusVehicleChargingEnabled) {
#ifdef DEBUG_VIA_USB
Serial.println("STATE SHIFT to EVSE_START reached in process_vehicle_charging_session()");
#ifdef DEBUG_LOG
logging.println("STATE SHIFT to EVSE_START reached in process_vehicle_charging_session()");
#endif
CHADEMO_Status = CHADEMO_EVSE_START;
return;
}
if (vehicle_permission && evse_permission && CHADEMO_Status == CHADEMO_POWERFLOW) {
#ifdef DEBUG_VIA_USB
Serial.println("updating vehicle request in process_vehicle_charging_session()");
#ifdef DEBUG_LOG
logging.println("updating vehicle request in process_vehicle_charging_session()");
#endif
return;
}
#ifdef DEBUG_VIA_USB
Serial.println("UNHANDLED STATE IN process_vehicle_charging_session()");
#ifdef DEBUG_LOG
logging.println("UNHANDLED STATE IN process_vehicle_charging_session()");
#endif
return;
}
@ -308,24 +308,24 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
inline void process_vehicle_charging_limits(CAN_frame rx_frame) {
x200_discharge_limits.MaximumDischargeCurrent = rx_frame.data.u8[0];
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
x200_discharge_limits.MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
/* unsigned long currentMillis = millis();
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis;
Serial.println("x200 Max remaining capacity for charging/discharging:");
logging.println("x200 Max remaining capacity for charging/discharging:");
// initially this is set to 0, which is represented as 0xFF
Serial.println(0xFF - x200_discharge_limits.MaxRemainingCapacityForCharging);
logging.println(0xFF - x200_discharge_limits.MaxRemainingCapacityForCharging);
}
*/
#endif
if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage && CHADEMO_Status > CHADEMO_NEGOTIATE) {
#ifdef DEBUG_VIA_USB
Serial.println("x200 minimum discharge voltage met or exceeded, stopping.");
#ifdef DEBUG_LOG
logging.println("x200 minimum discharge voltage met or exceeded, stopping.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
@ -338,16 +338,16 @@ inline void process_vehicle_discharge_estimate(CAN_frame rx_frame) {
unsigned long currentMillis = millis();
x201_discharge_estimate.V2HchargeDischargeSequenceNum = rx_frame.data.u8[0];
x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[3]);
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis;
Serial.println("x201 availabile vehicle energy, completion time");
Serial.println(x201_discharge_estimate.AvailableVehicleEnergy);
Serial.println("x201 approx vehicle completion time");
Serial.println(x201_discharge_estimate.ApproxDischargeCompletionTime);
logging.print("x201 availabile vehicle energy, completion time: ");
logging.println(x201_discharge_estimate.AvailableVehicleEnergy);
logging.print("x201 approx vehicle completion time: ");
logging.println(x201_discharge_estimate.ApproxDischargeCompletionTime);
}
#endif
}
@ -364,22 +364,22 @@ inline void process_vehicle_dynamic_control(CAN_frame rx_frame) {
inline void process_vehicle_vendor_ID(CAN_frame rx_frame) {
x700_vendor_id.AutomakerCode = rx_frame.data.u8[0];
x700_vendor_id.OptionalContent =
((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]); //Actually more bytes, but not needed for our purpose
((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]); //Actually more bytes, but not needed for our purpose
}
void receive_can_battery(CAN_frame rx_frame) {
#ifdef CH_CAN_DEBUG
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.ID, HEX);
Serial.print(" ");
Serial.print(rx_frame.DLC);
Serial.print(" ");
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" ");
logging.print(rx_frame.ID, HEX);
logging.print(" ");
logging.print(rx_frame.DLC);
logging.print(" ");
for (int i = 0; i < rx_frame.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
logging.print(rx_frame.data.u8[i], HEX);
logging.print(" ");
}
Serial.println("");
logging.println("");
#endif
// CHADEMO coexists with a CAN-based shunt. Only process CHADEMO-specific IDs
@ -557,7 +557,7 @@ void update_evse_status(CAN_frame& f) {
*
*/
if ((x102_chg_session.TargetBatteryVoltage > x108_evse_cap.available_output_voltage) ||
(x100_chg_lim.MaximumBatteryVoltage > x108_evse_cap.threshold_voltage)) {
(x100_chg_lim.MaximumBatteryVoltage < x108_evse_cap.threshold_voltage)) {
//Toggle battery incompatibility flag 109.5.3
x109_evse_state.s.status.EVSE_error = 1;
x109_evse_state.s.status.battery_incompatible = 1;
@ -602,7 +602,8 @@ void update_evse_discharge_estimate(CAN_frame& f) {
*/
CHADEMO_209.data.u8[0] = x209_evse_dischg_est.sequence_control_number;
CHADEMO_209.data.u8[1] = x209_evse_dischg_est.remaining_discharge_time;
CHADEMO_209.data.u8[1] = lowByte(x209_evse_dischg_est.remaining_discharge_time);
CHADEMO_209.data.u8[2] = highByte(x209_evse_dischg_est.remaining_discharge_time);
}
/* x208 EVSE, peer to 0x200 Vehicle */
@ -712,9 +713,9 @@ void send_can_battery() {
// TODO need an update_evse_dynamic_control(..) function above before we send 118
// 110.0.0
if (x102_chg_session.ControlProtocolNumberEV >= 0x03) { //Only send the following on Chademo 2.0 vehicles?
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
//FIXME REMOVE
Serial.println("REMOVE: proto 2.0");
logging.println("REMOVE: proto 2.0");
#endif
transmit_can(&CHADEMO_118, can_config.battery);
}
@ -751,16 +752,16 @@ void handle_chademo_sequence() {
/* ------------------- State override conditions checks ------------------- */
/* ------------------------------------------------------------------------------ */
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !x102_chg_session.s.status.StatusVehicleShifterPosition) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle is not parked, abort.");
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && x102_chg_session.s.status.StatusVehicleShifterPosition) {
#ifdef DEBUG_LOG
logging.println("Vehicle is not parked, abort.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !vehicle_permission) {
#ifdef DEBUG_VIA_USB
Serial.println("Vehicle charge/discharge permission ended, stop.");
#ifdef DEBUG_LOG
logging.println("Vehicle charge/discharge permission ended, stop.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
@ -774,25 +775,24 @@ void handle_chademo_sequence() {
plug_inserted = digitalRead(CHADEMO_PIN_7);
if (!plug_inserted) {
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
// Serial.println("CHADEMO plug is not inserted.");
//
// logging.println("CHADEMO plug is not inserted.");
#endif
return;
}
CHADEMO_Status = CHADEMO_CONNECTED;
#ifdef DEBUG_VIA_USB
Serial.println("CHADEMO plug is inserted. Provide EVSE power to vehicle to trigger initialization.");
#ifdef DEBUG_LOG
logging.println("CHADEMO plug is inserted. Provide EVSE power to vehicle to trigger initialization.");
#endif
break;
case CHADEMO_CONNECTED:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
//Serial.println("CHADEMO_CONNECTED State");
//logging.println("CHADEMO_CONNECTED State");
#endif
/* plug_inserted is .. essentially a volatile of sorts, so verify */
if (plug_inserted) {
@ -810,8 +810,8 @@ void handle_chademo_sequence() {
* with timers to have higher confidence of certain conditions hitting
* a steady state
*/
#ifdef DEBUG_VIA_USB
Serial.println("CHADEMO plug is not inserted, cannot connect d2 relay to begin initialization.");
#ifdef DEBUG_LOG
logging.println("CHADEMO plug is not inserted, cannot connect d2 relay to begin initialization.");
#endif
CHADEMO_Status = CHADEMO_IDLE;
}
@ -821,8 +821,8 @@ void handle_chademo_sequence() {
* Used for triggers/error handling elsewhere;
* State change to CHADEMO_NEGOTIATE occurs in receive_can_battery(..)
*/
#ifdef DEBUG_VIA_USB
// Serial.println("Awaiting initial vehicle CAN to trigger negotiation");
#ifdef DEBUG_LOG
// logging.println("Awaiting initial vehicle CAN to trigger negotiation");
#endif
evse_init();
break;
@ -830,16 +830,16 @@ void handle_chademo_sequence() {
/* Vehicle and EVSE dance */
//TODO if pin 4 / j goes high,
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
// Serial.println("CHADEMO_NEGOTIATE State");
// logging.println("CHADEMO_NEGOTIATE State");
#endif
x109_evse_state.s.status.ChgDischStopControl = 1;
break;
case CHADEMO_EV_ALLOWED:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_EV_ALLOWED State");
logging.println("CHADEMO_EV_ALLOWED State");
#endif
// If we are in this state, vehicle_permission was already set to true...but re-verify
// that pin 4 (j) reads high
@ -855,9 +855,9 @@ void handle_chademo_sequence() {
}
break;
case CHADEMO_EVSE_PREPARE:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_EVSE_PREPARE State");
logging.println("CHADEMO_EVSE_PREPARE State");
#endif
/* TODO voltage check of output < 20v
* insulation test hypothetically happens here before triggering PIN 10 high
@ -878,7 +878,7 @@ void handle_chademo_sequence() {
digitalWrite(CHADEMO_PIN_10, HIGH);
evse_permission = true;
} else {
Serial.println("Insulation check measures > 20v ");
logging.println("Insulation check measures > 20v ");
}
// likely unnecessary but just to be sure. consider removal
@ -891,9 +891,9 @@ void handle_chademo_sequence() {
//state changes to CHADEMO_EVSE_START only upon receipt of charging session request
break;
case CHADEMO_EVSE_START:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_EVSE_START State");
logging.println("CHADEMO_EVSE_START State");
#endif
datalayer.system.status.battery_allows_contactor_closing = true;
x109_evse_state.s.status.ChgDischStopControl = 1;
@ -901,8 +901,8 @@ void handle_chademo_sequence() {
CHADEMO_Status = CHADEMO_EVSE_CONTACTORS_ENABLED;
#ifdef DEBUG_VIA_USB
Serial.println("Initiating contactors");
#ifdef DEBUG_LOG
logging.println("Initiating contactors");
#endif
/* break rather than fall through because contactors are not instantaneous;
@ -911,17 +911,17 @@ void handle_chademo_sequence() {
break;
case CHADEMO_EVSE_CONTACTORS_ENABLED:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_EVSE_CONTACTORS State");
logging.println("CHADEMO_EVSE_CONTACTORS State");
#endif
/* check whether contactors ready, because externally dependent upon inverter allow during discharge */
if (contactors_ready) {
#ifdef DEBUG_VIA_USB
Serial.println("Contactors ready");
Serial.print("Voltage: ");
Serial.println(get_measured_voltage());
#ifdef DEBUG_LOG
logging.println("Contactors ready");
logging.print("Voltage: ");
logging.println(get_measured_voltage());
#endif
/* transition to POWERFLOW state if discharge compatible on both sides */
if (x109_evse_state.discharge_compatible && x102_chg_session.s.status.StatusVehicleDischargeCompatible &&
@ -941,9 +941,9 @@ void handle_chademo_sequence() {
/* break or fall through ? TODO */
break;
case CHADEMO_POWERFLOW:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_POWERFLOW State");
logging.println("CHADEMO_POWERFLOW State");
#endif
/* POWERFLOW for charging, discharging, and bidirectional */
/* Interpretation */
@ -961,8 +961,8 @@ void handle_chademo_sequence() {
}
if (get_measured_voltage() <= x200_discharge_limits.MinimumDischargeVoltage) {
#ifdef DEBUG_VIA_USB
Serial.println("x200 minimum discharge voltage met or exceeded, stopping.");
#ifdef DEBUG_LOG
logging.println("x200 minimum discharge voltage met or exceeded, stopping.");
#endif
CHADEMO_Status = CHADEMO_STOP;
}
@ -972,9 +972,9 @@ void handle_chademo_sequence() {
x109_evse_state.s.status.EVSE_status = 1;
break;
case CHADEMO_STOP:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_STOP State");
logging.println("CHADEMO_STOP State");
#endif
/* back to CHADEMO_IDLE after teardown */
x109_evse_state.s.status.ChgDischStopControl = 1;
@ -1000,16 +1000,16 @@ void handle_chademo_sequence() {
break;
case CHADEMO_FAULT:
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
// Commented unless needed for debug
Serial.println("CHADEMO_FAULT State");
logging.println("CHADEMO_FAULT State");
#endif
/* Once faulted, never departs CHADEMO_FAULT state unless device is power cycled as a safety measure */
x109_evse_state.s.status.EVSE_error = 1;
x109_evse_state.s.status.ChgDischError = 1;
x109_evse_state.s.status.ChgDischStopControl = 1;
#ifdef DEBUG_VIA_USB
Serial.println("CHADEMO fault encountered, tearing down to make safe");
#ifdef DEBUG_LOG
logging.println("CHADEMO fault encountered, tearing down to make safe");
#endif
digitalWrite(CHADEMO_PIN_10, LOW);
digitalWrite(CHADEMO_PIN_2, LOW);
@ -1020,8 +1020,8 @@ void handle_chademo_sequence() {
break;
default:
#ifdef DEBUG_VIA_USB
Serial.println("UNHANDLED CHADEMO_STATE, setting FAULT");
#ifdef DEBUG_LOG
logging.println("UNHANDLED CHADEMO_STATE, setting FAULT");
#endif
CHADEMO_Status = CHADEMO_FAULT;
break;
@ -1031,9 +1031,18 @@ void handle_chademo_sequence() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Chademo battery selected");
#endif
pinMode(CHADEMO_PIN_2, OUTPUT);
digitalWrite(CHADEMO_PIN_2, LOW);
pinMode(CHADEMO_PIN_10, OUTPUT);
digitalWrite(CHADEMO_PIN_10, LOW);
pinMode(CHADEMO_LOCK, OUTPUT);
digitalWrite(CHADEMO_LOCK, LOW);
pinMode(CHADEMO_PIN_4, INPUT);
pinMode(CHADEMO_PIN_7, INPUT);
strncpy(datalayer.system.info.battery_protocol, "Chademo V2X mode", 63);
datalayer.system.info.battery_protocol[63] = '\0';
CHADEMO_Status = CHADEMO_IDLE;
@ -1075,6 +1084,9 @@ void setup_battery(void) { // Performs one time setup at startup
x109_evse_state.s.status.ChgDischStopControl = 1;
handle_chademo_sequence();
// ISA_deFAULT(); // ISA Setup - it is sufficient to set it once, because it is saved in SUNT
// ISA_initialize(); // ISA Setup - it is sufficient to set it once, because it is saved in SUNT
// ISA_RESTART();
setupMillis = millis();
}

View file

@ -4,7 +4,6 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 9999
//Contactor control is required for CHADEMO support
#define CONTACTOR_CONTROL

View file

@ -12,6 +12,11 @@
*
* 2024 - Modified to make use of ESP32-Arduino-CAN by miwagner
*
* 2024.11 - Modified byte sequence to Big Endian (this is the default for IVT) and the same as CHAdeMO
* - Fixed and Added send functions
* - Added some GET functions
* by NJbubo
*
*/
#include "../include.h"
#ifdef CHADEMO_BATTERY
@ -74,16 +79,29 @@ uint16_t get_measured_current() {
}
//This is our CAN interrupt service routine to catch inbound frames
inline void ISA_handleFrame(CAN_frame* frame) {
void ISA_handleFrame(CAN_frame* frame) {
if (frame->ID < 0x521 || frame->ID > 0x528) {
if (frame->ID < 0x510 || frame->ID > 0x528) {
return;
}
framecount++;
switch (frame->ID) {
case 0x510:
case 0x511:
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" ");
logging.print(frame->ID, HEX);
logging.print(" ");
logging.print(frame->DLC);
logging.print(" ");
for (int i = 0; i < frame->DLC; ++i) {
logging.print(frame->data.u8[i], HEX);
logging.print(" ");
}
logging.println("");
break;
case 0x521:
@ -118,7 +136,6 @@ inline void ISA_handleFrame(CAN_frame* frame) {
ISA_handle528(frame);
break;
}
return;
}
@ -126,7 +143,7 @@ inline void ISA_handleFrame(CAN_frame* frame) {
inline void ISA_handle521(CAN_frame* frame) {
long current = 0;
current =
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
milliamps = current;
Amperes = current / 1000.0f;
@ -135,7 +152,7 @@ inline void ISA_handle521(CAN_frame* frame) {
//handle frame for Voltage
inline void ISA_handle522(CAN_frame* frame) {
long volt =
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
Voltage = volt / 1000.0f;
Voltage1 = Voltage - (Voltage2 + Voltage3);
@ -158,7 +175,7 @@ inline void ISA_handle522(CAN_frame* frame) {
//handle frame for Voltage 2
inline void ISA_handle523(CAN_frame* frame) {
long volt =
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
Voltage2 = volt / 1000.0f;
if (Voltage2 > 3)
@ -177,7 +194,7 @@ inline void ISA_handle523(CAN_frame* frame) {
//handle frame for Voltage3
inline void ISA_handle524(CAN_frame* frame) {
long volt =
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
Voltage3 = volt / 1000.0f;
@ -194,7 +211,7 @@ inline void ISA_handle524(CAN_frame* frame) {
//handle frame for Temperature
inline void ISA_handle525(CAN_frame* frame) {
long temp = 0;
temp = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
temp = (long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
Temperature = temp / 10;
}
@ -202,14 +219,15 @@ inline void ISA_handle525(CAN_frame* frame) {
//handle frame for Kilowatts
inline void ISA_handle526(CAN_frame* frame) {
watt = 0;
watt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
watt = (long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
KW = watt / 1000.0f;
}
//handle frame for Ampere-Hours
inline void ISA_handle527(CAN_frame* frame) {
As = 0;
As = (frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]);
As = (long)(frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]);
AH += (As - lastAs) / 3600.0f;
lastAs = As;
@ -217,133 +235,201 @@ inline void ISA_handle527(CAN_frame* frame) {
//handle frame for kiloWatt-hours
inline void ISA_handle528(CAN_frame* frame) {
wh = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
wh = (long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
KWH += (wh - lastWh) / 1000.0f;
lastWh = wh;
}
/*
void ISA_initialize() {
firstframe=false;
STOP();
delay(700);
for(int i=0;i<9;i++) {
Serial.println("initialization \n");
firstframe = false;
ISA_STOP();
delay(500);
for (int i = 0; i < 8; i++) {
logging.print("ISA Initialization ");
logging.println(i);
outframe.data.u8[0]=(0x20+i);
outframe.data.u8[1]=0x42;
outframe.data.u8[2]=0x02;
outframe.data.u8[3]=(0x60+(i*18));
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
outframe.data.u8[0] = (0x20 + i);
outframe.data.u8[1] = 0x02;
outframe.data.u8[2] = 0x02;
outframe.data.u8[3] = (0x60 + (i * 18));
outframe.data.u8[4] = 0x00;
outframe.data.u8[5] = 0x00;
outframe.data.u8[6] = 0x00;
outframe.data.u8[7] = 0x00;
transmit_can((&outframe, can_config.battery);
delay(500);
sendSTORE();
delay(500);
}
START();
transmit_can(&outframe, can_config.battery);
delay(500);
lastAs=As;
lastWh=wh;
}
ISA_sendSTORE();
delay(500);
ISA_START();
delay(500);
lastAs = As;
lastWh = wh;
}
void ISA_STOP() {
outframe.data.u8[0]=0x34;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
transmit_can((&outframe, can_config.battery);
logging.println("ISA STOP");
outframe.data.u8[0] = 0x34;
outframe.data.u8[1] = 0x00;
outframe.data.u8[2] = 0x01;
outframe.data.u8[3] = 0x00;
outframe.data.u8[4] = 0x00;
outframe.data.u8[5] = 0x00;
outframe.data.u8[6] = 0x00;
outframe.data.u8[7] = 0x00;
transmit_can(&outframe, can_config.battery);
}
void ISA_sendSTORE() {
outframe.data.u8[0]=0x32;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
transmit_can((&outframe, can_config.battery);
logging.println("ISA send STORE");
outframe.data.u8[0] = 0x32;
outframe.data.u8[1] = 0x00;
outframe.data.u8[2] = 0x00;
outframe.data.u8[3] = 0x00;
outframe.data.u8[4] = 0x00;
outframe.data.u8[5] = 0x00;
outframe.data.u8[6] = 0x00;
outframe.data.u8[7] = 0x00;
transmit_can(&outframe, can_config.battery);
}
void ISA_START() {
outframe.data.u8[0]=0x34;
outframe.data.u8[1]=0x01;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
transmit_can((&outframe, can_config.battery);
logging.println("ISA START");
outframe.data.u8[0] = 0x34;
outframe.data.u8[1] = 0x01;
outframe.data.u8[2] = 0x01;
outframe.data.u8[3] = 0x00;
outframe.data.u8[4] = 0x00;
outframe.data.u8[5] = 0x00;
outframe.data.u8[6] = 0x00;
outframe.data.u8[7] = 0x00;
transmit_can(&outframe, can_config.battery);
}
void ISA_RESTART() {
//Has the effect of zeroing AH and KWH
outframe.data.u8[0]=0x3F;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
transmit_can((&outframe, can_config.battery);
//Has the effect of zeroing AH and KWH
logging.println("ISA RESTART");
outframe.data.u8[0] = 0x3F;
outframe.data.u8[1] = 0x00;
outframe.data.u8[2] = 0x00;
outframe.data.u8[3] = 0x00;
outframe.data.u8[4] = 0x00;
outframe.data.u8[5] = 0x00;
outframe.data.u8[6] = 0x00;
outframe.data.u8[7] = 0x00;
transmit_can(&outframe, can_config.battery);
}
void ISA_deFAULT() {
//Returns module to original defaults
outframe.data.u8[0]=0x3D;
outframe.data.u8[1]=0x00;
outframe.data.u8[2]=0x00;
outframe.data.u8[3]=0x00;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
transmit_can((&outframe, can_config.battery);
//Returns module to original defaults
ISA_STOP();
delay(500);
logging.println("ISA RESTART to default");
outframe.data.u8[0] = 0x3D;
outframe.data.u8[1] = 0x00;
outframe.data.u8[2] = 0x00;
outframe.data.u8[3] = 0x00;
outframe.data.u8[4] = 0x00;
outframe.data.u8[5] = 0x00;
outframe.data.u8[6] = 0x00;
outframe.data.u8[7] = 0x00;
transmit_can(&outframe, can_config.battery);
delay(500);
ISA_START();
delay(500);
}
void ISA_initCurrent() {
STOP();
delay(500);
Serial.println("initialization \n");
outframe.data.u8[0]=0x21;
outframe.data.u8[1]=0x42;
outframe.data.u8[2]=0x01;
outframe.data.u8[3]=0x61;
outframe.data.u8[4]=0x00;
outframe.data.u8[5]=0x00;
outframe.data.u8[6]=0x00;
outframe.data.u8[7]=0x00;
ISA_STOP();
delay(500);
transmit_can((&outframe, can_config.battery);
logging.println("ISA Initialization Current");
delay(500);
outframe.data.u8[0] = 0x21;
outframe.data.u8[1] = 0x02;
outframe.data.u8[2] = 0x01;
outframe.data.u8[3] = 0x61;
outframe.data.u8[4] = 0x00;
outframe.data.u8[5] = 0x00;
outframe.data.u8[6] = 0x00;
outframe.data.u8[7] = 0x00;
sendSTORE();
delay(500);
transmit_can(&outframe, can_config.battery);
delay(500);
START();
delay(500);
lastAs=As;
lastWh=wh;
ISA_sendSTORE();
delay(500);
ISA_START();
delay(500);
lastAs = As;
lastWh = wh;
}
*/
void ISA_getCONFIG(uint8_t i) {
logging.print("ISA Get Config ");
logging.println(i);
outframe.data.u8[0] = (0x60 + i);
outframe.data.u8[1] = 0x00;
outframe.data.u8[2] = 0x00;
outframe.data.u8[3] = 0x00;
outframe.data.u8[4] = 0x00;
outframe.data.u8[5] = 0x00;
outframe.data.u8[6] = 0x00;
outframe.data.u8[7] = 0x00;
transmit_can(&outframe, can_config.battery);
}
void ISA_getCAN_ID(uint8_t i) {
logging.print("ISA Get CAN ID ");
logging.println(i);
outframe.data.u8[0] = (0x50 + i);
if (i == 8)
outframe.data.u8[0] = 0x5D;
if (i == 9)
outframe.data.u8[0] = 0x5F;
outframe.data.u8[1] = 0x00;
outframe.data.u8[2] = 0x00;
outframe.data.u8[3] = 0x00;
outframe.data.u8[4] = 0x00;
outframe.data.u8[5] = 0x00;
outframe.data.u8[6] = 0x00;
outframe.data.u8[7] = 0x00;
transmit_can(&outframe, can_config.battery);
}
void ISA_getINFO(uint8_t i) {
logging.print("ISA Get INFO ");
logging.println(i, HEX);
outframe.data.u8[0] = (0x70 + i);
outframe.data.u8[1] = 0x00;
outframe.data.u8[2] = 0x00;
outframe.data.u8[3] = 0x00;
outframe.data.u8[4] = 0x00;
outframe.data.u8[5] = 0x00;
outframe.data.u8[6] = 0x00;
outframe.data.u8[7] = 0x00;
transmit_can(&outframe, can_config.battery);
}
#endif

View file

@ -5,7 +5,7 @@
uint16_t get_measured_voltage();
uint16_t get_measured_current();
inline void ISA_handler(CAN_frame* frame);
void ISA_handleFrame(CAN_frame* frame);
inline void ISA_handle521(CAN_frame* frame);
inline void ISA_handle522(CAN_frame* frame);
inline void ISA_handle523(CAN_frame* frame);
@ -14,6 +14,16 @@ inline void ISA_handle525(CAN_frame* frame);
inline void ISA_handle526(CAN_frame* frame);
inline void ISA_handle527(CAN_frame* frame);
inline void ISA_handle528(CAN_frame* frame);
void ISA_initialize();
void ISA_STOP();
void ISA_sendSTORE();
void ISA_START();
void ISA_RESTART();
void ISA_deFAULT();
void ISA_initCurrent();
void ISA_getCONFIG(uint8_t i);
void ISA_getCAN_ID(uint8_t i);
void ISA_getINFO(uint8_t i);
void transmit_can(CAN_frame* tx_frame, int interface);

View file

@ -8,8 +8,6 @@
//Figure out if CAN messages need to be sent to keep the system happy?
/* Do not change code below unless you are sure what you are doing */
#define MAX_CELL_VOLTAGE 4150
#define MIN_CELL_VOLTAGE 2750
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
static uint8_t BMU_Detected = 0;
static uint8_t CMU_Detected = 0;
@ -53,8 +51,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_discharge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded
datalayer.battery.status.active_power_W = BMU_Power; //TODO: Scaling?
static int n = sizeof(cell_voltages) / sizeof(cell_voltages[0]);
max_volt_cel = cell_voltages[0]; // Initialize max with the first element of the array
for (int i = 1; i < n; i++) {
@ -89,12 +85,12 @@ void update_values_battery() { //This function maps all the values fetched via
for (int i = 0; i < 88; ++i) {
datalayer.battery.status.cell_voltages_mV[i] = (uint16_t)(cell_voltages[i] * 1000);
}
if (max_volt_cel > 2200) { // Only update cellvoltage when we have a value
datalayer.battery.info.number_of_cells = 88;
if (max_volt_cel > 2.2) { // Only update cellvoltage when we have a value
datalayer.battery.status.cell_max_voltage_mV = (uint16_t)(max_volt_cel * 1000);
}
if (min_volt_cel > 2200) { // Only update cellvoltage when we have a value
if (min_volt_cel > 2.2) { // Only update cellvoltage when we have a value
datalayer.battery.status.cell_min_voltage_mV = (uint16_t)(min_volt_cel * 1000);
}
@ -106,38 +102,30 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.temperature_max_dC = (int16_t)(max_temp_cel * 10);
}
//Check safeties
if (datalayer.battery.status.cell_max_voltage_mV >= MAX_CELL_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, datalayer.battery.status.cell_max_voltage_mV);
}
if (datalayer.battery.status.cell_min_voltage_mV <= MIN_CELL_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, datalayer.battery.status.cell_min_voltage_mV);
}
if (!BMU_Detected) {
#ifdef DEBUG_VIA_USB
Serial.println("BMU not detected, check wiring!");
#ifdef DEBUG_LOG
logging.println("BMU not detected, check wiring!");
#endif
}
#ifdef DEBUG_VIA_USB
Serial.println("Battery Values");
Serial.print("BMU SOC: ");
Serial.print(BMU_SOC);
Serial.print(" BMU Current: ");
Serial.print(BMU_Current);
Serial.print(" BMU Battery Voltage: ");
Serial.print(BMU_PackVoltage);
Serial.print(" BMU_Power: ");
Serial.print(BMU_Power);
Serial.print(" Cell max voltage: ");
Serial.print(max_volt_cel);
Serial.print(" Cell min voltage: ");
Serial.print(min_volt_cel);
Serial.print(" Cell max temp: ");
Serial.print(max_temp_cel);
Serial.print(" Cell min temp: ");
Serial.println(min_temp_cel);
#ifdef DEBUG_LOG
logging.println("Battery Values");
logging.print("BMU SOC: ");
logging.print(BMU_SOC);
logging.print(" BMU Current: ");
logging.print(BMU_Current);
logging.print(" BMU Battery Voltage: ");
logging.print(BMU_PackVoltage);
logging.print(" BMU_Power: ");
logging.print(BMU_Power);
logging.print(" Cell max voltage: ");
logging.print(max_volt_cel);
logging.print(" Cell min voltage: ");
logging.print(min_volt_cel);
logging.print(" Cell max temp: ");
logging.print(max_temp_cel);
logging.print(" Cell min temp: ");
logging.println(min_temp_cel);
#endif
}
@ -236,12 +224,13 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Mitsubishi i-MiEV / Citroen C-Zero / Peugeot Ion battery selected");
#endif
datalayer.battery.info.max_design_voltage_dV = 3696; // 369.6V
datalayer.battery.info.min_design_voltage_dV = 3160; // 316.0V
strncpy(datalayer.system.info.battery_protocol, "I-Miev / C-Zero / Ion Triplet", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}
#endif

View file

@ -4,7 +4,11 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 3696 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3160
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_VOLTAGE_MV 4150 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2750 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);

View file

@ -57,9 +57,9 @@ CAN_frame ipace_keep_alive = {.FD = false,
.data = {0x9E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};*/
void print_units(char* header, int value, char* units) {
Serial.print(header);
Serial.print(value);
Serial.print(units);
logging.print(header);
logging.print(value);
logging.print(units);
}
void update_values_battery() {
@ -81,10 +81,6 @@ void update_values_battery() {
datalayer.battery.status.cell_min_voltage_mV = HVBattCellVoltageMinMv;
//Power in watts, Negative = charging batt
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.temperature_min_dC = HVBattCellTempColdest * 10; // C to dC
datalayer.battery.status.temperature_max_dC = HVBattCellTempHottest * 10; // C to dC
@ -108,8 +104,8 @@ void update_values_battery() {
}
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB
Serial.println("Values going to inverter");
#ifdef DEBUG_LOG
logging.println("Values going to inverter");
print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% ");
print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% ");
print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V ");
@ -119,7 +115,7 @@ void update_values_battery() {
print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV ");
Serial.println("");
logging.println("");
#endif
}
@ -233,17 +229,17 @@ void receive_can_battery(CAN_frame rx_frame) {
}
// All CAN messages recieved will be logged via serial
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.ID, HEX);
Serial.print(" ");
Serial.print(rx_frame.DLC);
Serial.print(" ");
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" ");
logging.print(rx_frame.ID, HEX);
logging.print(" ");
logging.print(rx_frame.DLC);
logging.print(" ");
for (int i = 0; i < rx_frame.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
logging.print(rx_frame.data.u8[i], HEX);
logging.print(" ");
}
Serial.println("");
logging.println("");
}
void send_can_battery() {
@ -258,13 +254,16 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Jaguar iPace 90kWh battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Jaguar I-PACE", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 108;
datalayer.battery.info.max_design_voltage_dV = 4546;
datalayer.battery.info.min_design_voltage_dV = 3370;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif

View file

@ -3,7 +3,11 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 9999 // TODO is this ok ?
#define MAX_PACK_VOLTAGE_DV 4546 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3370
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);

File diff suppressed because it is too large Load diff

View file

@ -7,9 +7,15 @@
extern ACAN2517FD canfd;
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 8064 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 4320
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2950 //Battery is put into emergency stop if one cell goes below this value
#define MAXCHARGEPOWERALLOWED 10000
#define MAXDISCHARGEPOWERALLOWED 10000
#define RAMPDOWN_SOC 9000 // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00%
#define RAMPDOWNPOWERALLOWED 10000 // What power we ramp down from towards top balancing
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);

View file

@ -8,9 +8,6 @@
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis10 = 0; // will store last time a 10s CAN Message was send
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
static uint16_t soc_calculated = 0;
static uint16_t SOC_BMS = 0;
static uint16_t SOC_Display = 0;
@ -127,10 +124,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_discharge_power_W = allowedDischargePower * 10;
//Power in watts, Negative = charging batt
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery.status.temperature_max_dC = (int8_t)temperatureMax * 10; //Increase decimals, 18C -> 18.0C
@ -147,73 +140,65 @@ void update_values_battery() { //This function maps all the values fetched via
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
}
// Check if cell voltages are within allowed range
if (CellVoltMax_mV >= MAX_CELL_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (CellVoltMin_mV <= MIN_CELL_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
/* Safeties verified. Perform USB serial printout if configured to do so */
#ifdef DEBUG_VIA_USB
Serial.println(); //sepatator
Serial.println("Values from battery: ");
Serial.print("SOC BMS: ");
Serial.print((uint16_t)SOC_BMS / 10.0, 1);
Serial.print("% | SOC Display: ");
Serial.print((uint16_t)SOC_Display / 10.0, 1);
Serial.print("% | SOH ");
Serial.print((uint16_t)batterySOH / 10.0, 1);
Serial.println("%");
Serial.print((int16_t)batteryAmps / 10.0, 1);
Serial.print(" Amps | ");
Serial.print((uint16_t)batteryVoltage / 10.0, 1);
Serial.print(" Volts | ");
Serial.print((int16_t)datalayer.battery.status.active_power_W);
Serial.println(" Watts");
Serial.print("Allowed Charge ");
Serial.print((uint16_t)allowedChargePower * 10);
Serial.print(" W | Allowed Discharge ");
Serial.print((uint16_t)allowedDischargePower * 10);
Serial.println(" W");
Serial.print("MaxCellVolt ");
Serial.print(CellVoltMax_mV);
Serial.print(" mV No ");
Serial.print(CellVmaxNo);
Serial.print(" | MinCellVolt ");
Serial.print(CellVoltMin_mV);
Serial.print(" mV No ");
Serial.println(CellVminNo);
Serial.print("TempHi ");
Serial.print((int16_t)temperatureMax);
Serial.print("°C TempLo ");
Serial.print((int16_t)temperatureMin);
Serial.print("°C WaterInlet ");
Serial.print((int8_t)temperature_water_inlet);
Serial.print("°C PowerRelay ");
Serial.print((int8_t)powerRelayTemperature * 2);
Serial.println("°C");
Serial.print("Aux12volt: ");
Serial.print((int16_t)leadAcidBatteryVoltage / 10.0, 1);
Serial.println("V | ");
Serial.print("BmsManagementMode ");
Serial.print((uint8_t)batteryManagementMode, BIN);
#ifdef DEBUG_LOG
logging.println(); //sepatator
logging.println("Values from battery: ");
logging.print("SOC BMS: ");
logging.print((uint16_t)SOC_BMS / 10.0, 1);
logging.print("% | SOC Display: ");
logging.print((uint16_t)SOC_Display / 10.0, 1);
logging.print("% | SOH ");
logging.print((uint16_t)batterySOH / 10.0, 1);
logging.println("%");
logging.print((int16_t)batteryAmps / 10.0, 1);
logging.print(" Amps | ");
logging.print((uint16_t)batteryVoltage / 10.0, 1);
logging.print(" Volts | ");
logging.print((int16_t)datalayer.battery.status.active_power_W);
logging.println(" Watts");
logging.print("Allowed Charge ");
logging.print((uint16_t)allowedChargePower * 10);
logging.print(" W | Allowed Discharge ");
logging.print((uint16_t)allowedDischargePower * 10);
logging.println(" W");
logging.print("MaxCellVolt ");
logging.print(CellVoltMax_mV);
logging.print(" mV No ");
logging.print(CellVmaxNo);
logging.print(" | MinCellVolt ");
logging.print(CellVoltMin_mV);
logging.print(" mV No ");
logging.println(CellVminNo);
logging.print("TempHi ");
logging.print((int16_t)temperatureMax);
logging.print("°C TempLo ");
logging.print((int16_t)temperatureMin);
logging.print("°C WaterInlet ");
logging.print((int8_t)temperature_water_inlet);
logging.print("°C PowerRelay ");
logging.print((int8_t)powerRelayTemperature * 2);
logging.println("°C");
logging.print("Aux12volt: ");
logging.print((int16_t)leadAcidBatteryVoltage / 10.0, 1);
logging.println("V | ");
logging.print("BmsManagementMode ");
logging.print((uint8_t)batteryManagementMode, BIN);
if (bitRead((uint8_t)BMS_ign, 2) == 1) {
Serial.print(" | BmsIgnition ON");
logging.print(" | BmsIgnition ON");
} else {
Serial.print(" | BmsIgnition OFF");
logging.print(" | BmsIgnition OFF");
}
if (bitRead((uint8_t)batteryRelay, 0) == 1) {
Serial.print(" | PowerRelay ON");
logging.print(" | PowerRelay ON");
} else {
Serial.print(" | PowerRelay OFF");
logging.print(" | PowerRelay OFF");
}
Serial.print(" | Inverter ");
Serial.print(inverterVoltage);
Serial.println(" Volts");
logging.print(" | Inverter ");
logging.print(inverterVoltage);
logging.println(" Volts");
#endif
}
@ -223,12 +208,14 @@ void update_number_of_cells() {
// Check if we have 98S or 90S battery
if (datalayer.battery.status.cell_voltages_mV[97] > 0) {
datalayer.battery.info.number_of_cells = 98;
datalayer.battery.info.max_design_voltage_dV = 4040;
datalayer.battery.info.min_design_voltage_dV = 3100;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_98S_DV;
datalayer.battery.info.total_capacity_Wh = 64000;
} else {
datalayer.battery.info.number_of_cells = 90;
datalayer.battery.info.max_design_voltage_dV = 3870;
datalayer.battery.info.min_design_voltage_dV = 2250;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_90S_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV;
datalayer.battery.info.total_capacity_Wh = 40000;
}
}
}
@ -547,12 +534,12 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Kia Niro / Hyundai Kona 64kWh battery selected");
#endif
datalayer.battery.info.max_design_voltage_dV = 4040; // 404.0V
datalayer.battery.info.min_design_voltage_dV = 3100; // 310.0V
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; //Start with 98S value. Precised later
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; //Start with 90S value. Precised later
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
#endif

View file

@ -4,7 +4,13 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_98S_DV 4110 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_98S_DV 2800
#define MAX_PACK_VOLTAGE_90S_DV 3870
#define MIN_PACK_VOLTAGE_90S_DV 2250
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2950 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void update_number_of_cells();

View file

@ -68,10 +68,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W = available_charge_power * 10;
//Power in watts, Negative = charging batt
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.temperature_min_dC = (int16_t)(battery_module_min_temperature * 10);
datalayer.battery.status.temperature_max_dC = (int16_t)(battery_module_max_temperature * 10);
@ -261,12 +257,14 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Kia/Hyundai Hybrid battery selected");
#endif
datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV
datalayer.battery.info.max_design_voltage_dV = 2550; //TODO: Values OK?
datalayer.battery.info.min_design_voltage_dV = 1700;
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
#endif

View file

@ -4,7 +4,11 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 2550 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 1700
#define MAX_CELL_DEVIATION_MV 100
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,138 @@
#ifndef MEB_BATTERY_H
#define MEB_BATTERY_H
#include <Arduino.h>
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_84S_DV 3528 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_84S_DV 2520
#define MAX_PACK_VOLTAGE_96S_DV 4032
#define MIN_PACK_VOLTAGE_96S_DV 2880
#define MAX_PACK_VOLTAGE_108S_DV 4536
#define MIN_PACK_VOLTAGE_108S_DV 3240
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define PID_SOC 0x028C
#define PID_VOLTAGE 0x1E3B
#define PID_CURRENT 0x1E3D
#define PID_MAX_TEMP 0x1E0E
#define PID_MIN_TEMP 0x1E0F
#define PID_MAX_CHARGE_VOLTAGE 0x5171
#define PID_MIN_DISCHARGE_VOLTAGE 0x5170
#define PID_ALLOWED_CHARGE_POWER 0x1E1B
#define PID_ALLOWED_DISCHARGE_POWER 0x1E1C
#define PID_CELLVOLTAGE_CELL_1 0x1E40
#define PID_CELLVOLTAGE_CELL_2 0x1E41
#define PID_CELLVOLTAGE_CELL_3 0x1E42
#define PID_CELLVOLTAGE_CELL_4 0x1E43
#define PID_CELLVOLTAGE_CELL_5 0x1E44
#define PID_CELLVOLTAGE_CELL_6 0x1E45
#define PID_CELLVOLTAGE_CELL_7 0x1E46
#define PID_CELLVOLTAGE_CELL_8 0x1E47
#define PID_CELLVOLTAGE_CELL_9 0x1E48
#define PID_CELLVOLTAGE_CELL_10 0x1E49
#define PID_CELLVOLTAGE_CELL_11 0x1E4A
#define PID_CELLVOLTAGE_CELL_12 0x1E4B
#define PID_CELLVOLTAGE_CELL_13 0x1E4C
#define PID_CELLVOLTAGE_CELL_14 0x1E4D
#define PID_CELLVOLTAGE_CELL_15 0x1E4E
#define PID_CELLVOLTAGE_CELL_16 0x1E4F
#define PID_CELLVOLTAGE_CELL_17 0x1E50
#define PID_CELLVOLTAGE_CELL_18 0x1E51
#define PID_CELLVOLTAGE_CELL_19 0x1E52
#define PID_CELLVOLTAGE_CELL_20 0x1E53
#define PID_CELLVOLTAGE_CELL_21 0x1E54
#define PID_CELLVOLTAGE_CELL_22 0x1E55
#define PID_CELLVOLTAGE_CELL_23 0x1E56
#define PID_CELLVOLTAGE_CELL_24 0x1E57
#define PID_CELLVOLTAGE_CELL_25 0x1E58
#define PID_CELLVOLTAGE_CELL_26 0x1E59
#define PID_CELLVOLTAGE_CELL_27 0x1E5A
#define PID_CELLVOLTAGE_CELL_28 0x1E5B
#define PID_CELLVOLTAGE_CELL_29 0x1E5C
#define PID_CELLVOLTAGE_CELL_30 0x1E5D
#define PID_CELLVOLTAGE_CELL_31 0x1E5E
#define PID_CELLVOLTAGE_CELL_32 0x1E5F
#define PID_CELLVOLTAGE_CELL_33 0x1E60
#define PID_CELLVOLTAGE_CELL_34 0x1E61
#define PID_CELLVOLTAGE_CELL_35 0x1E62
#define PID_CELLVOLTAGE_CELL_36 0x1E63
#define PID_CELLVOLTAGE_CELL_37 0x1E64
#define PID_CELLVOLTAGE_CELL_38 0x1E65
#define PID_CELLVOLTAGE_CELL_39 0x1E66
#define PID_CELLVOLTAGE_CELL_40 0x1E67
#define PID_CELLVOLTAGE_CELL_41 0x1E68
#define PID_CELLVOLTAGE_CELL_42 0x1E69
#define PID_CELLVOLTAGE_CELL_43 0x1E6A
#define PID_CELLVOLTAGE_CELL_44 0x1E6B
#define PID_CELLVOLTAGE_CELL_45 0x1E6C
#define PID_CELLVOLTAGE_CELL_46 0x1E6D
#define PID_CELLVOLTAGE_CELL_47 0x1E6E
#define PID_CELLVOLTAGE_CELL_48 0x1E6F
#define PID_CELLVOLTAGE_CELL_49 0x1E70
#define PID_CELLVOLTAGE_CELL_50 0x1E71
#define PID_CELLVOLTAGE_CELL_51 0x1E72
#define PID_CELLVOLTAGE_CELL_52 0x1E73
#define PID_CELLVOLTAGE_CELL_53 0x1E74
#define PID_CELLVOLTAGE_CELL_54 0x1E75
#define PID_CELLVOLTAGE_CELL_55 0x1E76
#define PID_CELLVOLTAGE_CELL_56 0x1E77
#define PID_CELLVOLTAGE_CELL_57 0x1E78
#define PID_CELLVOLTAGE_CELL_58 0x1E79
#define PID_CELLVOLTAGE_CELL_59 0x1E7A
#define PID_CELLVOLTAGE_CELL_60 0x1E7B
#define PID_CELLVOLTAGE_CELL_61 0x1E7C
#define PID_CELLVOLTAGE_CELL_62 0x1E7D
#define PID_CELLVOLTAGE_CELL_63 0x1E7E
#define PID_CELLVOLTAGE_CELL_64 0x1E7F
#define PID_CELLVOLTAGE_CELL_65 0x1E80
#define PID_CELLVOLTAGE_CELL_66 0x1E81
#define PID_CELLVOLTAGE_CELL_67 0x1E82
#define PID_CELLVOLTAGE_CELL_68 0x1E83
#define PID_CELLVOLTAGE_CELL_69 0x1E84
#define PID_CELLVOLTAGE_CELL_70 0x1E85
#define PID_CELLVOLTAGE_CELL_71 0x1E86
#define PID_CELLVOLTAGE_CELL_72 0x1E87
#define PID_CELLVOLTAGE_CELL_73 0x1E88
#define PID_CELLVOLTAGE_CELL_74 0x1E89
#define PID_CELLVOLTAGE_CELL_75 0x1E8A
#define PID_CELLVOLTAGE_CELL_76 0x1E8B
#define PID_CELLVOLTAGE_CELL_77 0x1E8C
#define PID_CELLVOLTAGE_CELL_78 0x1E8D
#define PID_CELLVOLTAGE_CELL_79 0x1E8E
#define PID_CELLVOLTAGE_CELL_80 0x1E8F
#define PID_CELLVOLTAGE_CELL_81 0x1E90
#define PID_CELLVOLTAGE_CELL_82 0x1E91
#define PID_CELLVOLTAGE_CELL_83 0x1E92
#define PID_CELLVOLTAGE_CELL_84 0x1E93
#define PID_CELLVOLTAGE_CELL_85 0x1E94
#define PID_CELLVOLTAGE_CELL_86 0x1E95
#define PID_CELLVOLTAGE_CELL_87 0x1E96
#define PID_CELLVOLTAGE_CELL_88 0x1E97
#define PID_CELLVOLTAGE_CELL_89 0x1E98
#define PID_CELLVOLTAGE_CELL_90 0x1E99
#define PID_CELLVOLTAGE_CELL_91 0x1E9A
#define PID_CELLVOLTAGE_CELL_92 0x1E9B
#define PID_CELLVOLTAGE_CELL_93 0x1E9C
#define PID_CELLVOLTAGE_CELL_94 0x1E9D
#define PID_CELLVOLTAGE_CELL_95 0x1E9E
#define PID_CELLVOLTAGE_CELL_96 0x1E9F
#define PID_CELLVOLTAGE_CELL_97 0x1EA0
#define PID_CELLVOLTAGE_CELL_98 0x1EA1
#define PID_CELLVOLTAGE_CELL_99 0x1EA2
#define PID_CELLVOLTAGE_CELL_100 0x1EA3
#define PID_CELLVOLTAGE_CELL_101 0x1EA4
#define PID_CELLVOLTAGE_CELL_102 0x1EA5
#define PID_CELLVOLTAGE_CELL_103 0x1EA6
#define PID_CELLVOLTAGE_CELL_104 0x1EA7
#define PID_CELLVOLTAGE_CELL_105 0x1EA8
#define PID_CELLVOLTAGE_CELL_106 0x1EA9
#define PID_CELLVOLTAGE_CELL_107 0x1EAA
#define PID_CELLVOLTAGE_CELL_108 0x1EAB
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
#endif

View file

@ -39,15 +39,9 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W;
datalayer.battery.status.active_power_W;
datalayer.battery.status.temperature_min_dC;
datalayer.battery.status.temperature_max_dC;
#ifdef DEBUG_VIA_USB
#endif
}
void receive_can_battery(CAN_frame rx_frame) {
@ -137,12 +131,13 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("MG 5 battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = 4040; // Over this charging is not possible
datalayer.battery.info.min_design_voltage_dV = 3100; // Under this discharging is disabled
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
#endif

View file

@ -4,7 +4,11 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4040 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3100
#define MAX_CELL_DEVIATION_MV 150
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);

View file

@ -5,6 +5,7 @@
#include "../devboard/mqtt/mqtt.h"
#endif
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
#include "../devboard/utils/events.h"
/* Do not change code below unless you are sure what you are doing */
@ -37,11 +38,13 @@ CAN_frame LEAF_1D4 = {.FD = false,
.ID = 0x1D4,
.data = {0x6E, 0x6E, 0x00, 0x04, 0x07, 0x46, 0xE0, 0x44}};
// Active polling messages
uint8_t PIDgroups[] = {0x01, 0x02, 0x04, 0x83, 0x84, 0x90};
uint8_t PIDindex = 0;
CAN_frame LEAF_GROUP_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {2, 0x21, 1, 0, 0, 0, 0, 0}};
.data = {0x02, 0x21, PIDgroups[0], 0, 0, 0, 0, 0}};
CAN_frame LEAF_NEXT_LINE_REQUEST = {.FD = false,
.ext_ID = false,
.DLC = 8,
@ -75,9 +78,7 @@ static uint8_t crctable[256] = {
#define ZE1_BATTERY 2
static uint8_t LEAF_battery_Type = ZE0_BATTERY;
static bool battery_can_alive = false;
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
#define WH_PER_GID 77 //One GID is this amount of Watt hours
#define WH_PER_GID 77 //One GID is this amount of Watt hours
static uint16_t battery_Discharge_Power_Limit = 0; //Limit in kW
static uint16_t battery_Charge_Power_Limit = 0; //Limit in kW
static int16_t battery_MAX_POWER_FOR_CHARGER = 0; //Limit in kW
@ -108,7 +109,6 @@ static bool battery_Batt_Heater_Mail_Send_Request = false; //Stores info when a
// Nissan LEAF battery data from polled CAN messages
static uint8_t battery_request_idx = 0;
static uint8_t group_7bb = 0;
static uint8_t group = 1;
static bool stop_battery_query = true;
static uint8_t hold_off_with_polling_10seconds = 10;
static uint16_t battery_cell_voltages[97]; //array with all the cellvoltages
@ -125,7 +125,9 @@ static uint16_t battery_temp_raw_max = 0;
static uint16_t battery_temp_raw_min = 0;
static int16_t battery_temp_polled_max = 0;
static int16_t battery_temp_polled_min = 0;
static uint8_t BatterySerialNumber[15] = {0}; // Stores raw HEX values for ASCII chars
static uint8_t BatteryPartNumber[7] = {0}; // Stores raw HEX values for ASCII chars
static uint8_t BMSIDcode[8] = {0};
#ifdef DOUBLE_BATTERY
static uint8_t LEAF_battery2_Type = ZE0_BATTERY;
static bool battery2_can_alive = false;
@ -137,10 +139,10 @@ static uint16_t battery2_TEMP = 0; //Temporary value used in s
static uint16_t battery2_Wh_Remaining = 0; //Amount of energy in battery, in Wh
static uint16_t battery2_GIDS = 273; //Startup in 24kWh mode
static uint16_t battery2_MAX = 0;
static uint16_t battery2_Max_GIDS = 273; //Startup in 24kWh mode
static uint16_t battery2_StateOfHealth = 99; //State of health %
static uint16_t battery2_Total_Voltage2 = 740; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800]
static int16_t battery2_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400]
static uint16_t battery2_Max_GIDS = 273; //Startup in 24kWh mode
static uint16_t battery2_StateOfHealth = 99; //State of health %
static uint16_t battery2_Total_Voltage2 = 0; //Battery voltage (0-450V) [0.5V/bit, so actual range 0-800]
static int16_t battery2_Current2 = 0; //Battery current (-400-200A) [0.5A/bit, so actual range -800-400]
static int16_t battery2_HistData_Temperature_MAX = 6; //-40 to 86*C
static int16_t battery2_HistData_Temperature_MIN = 5; //-40 to 86*C
static int16_t battery2_AverageTemperature = 6; //Only available on ZE0, in celcius, -40 to +55
@ -175,11 +177,17 @@ static int16_t battery2_temp_polled_max = 0;
static int16_t battery2_temp_polled_min = 0;
#endif // DOUBLE_BATTERY
void print_with_units(char* header, int value, char* units) {
Serial.print(header);
Serial.print(value);
Serial.print(units);
}
// Clear SOH values
static uint8_t stateMachineClearSOH = 0xFF;
static uint32_t incomingChallenge = 0xFFFFFFFF;
static uint8_t solvedChallenge[8];
static bool challengeFailed = false;
CAN_frame LEAF_CLEAR_SOH = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
void update_values_battery() { /* This function maps all the values fetched via CAN to the correct parameters used for modbus */
/* Start with mapping all values */
@ -198,9 +206,6 @@ void update_values_battery() { /* This function maps all the values fetched via
datalayer.battery.status.remaining_capacity_Wh = battery_Wh_Remaining;
datalayer.battery.status.active_power_W = ((battery_Total_Voltage2 * battery_Current2) /
4); //P = U * I (Both values are 0.5 per bit so the math is non-intuitive)
//Update temperature readings. Method depends on which generation LEAF battery is used
if (LEAF_battery_Type == ZE0_BATTERY) {
//Since we only have average value, send the minimum as -1.0 degrees below average
@ -254,6 +259,12 @@ void update_values_battery() { /* This function maps all the values fetched via
clear_event(EVENT_BATTERY_EMPTY);
}
if (battery_Total_Voltage2 == 0x3FF) { //Battery reports critical measurement unavailable
set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, 0);
} else {
clear_event(EVENT_BATTERY_VALUE_UNAVAILABLE);
}
if (battery_Relay_Cut_Request) { //battery_FAIL, BMS requesting shutdown and contactors to be opened
//Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario
datalayer.battery.status.max_discharge_power_W = 0;
@ -317,16 +328,37 @@ void update_values_battery() { /* This function maps all the values fetched via
}
}
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB
Serial.println("Values from battery");
print_with_units("Real SOC%: ", (battery_SOC * 0.1), "% ");
print_with_units(", GIDS: ", battery_GIDS, " (x77Wh) ");
print_with_units(", Battery gen: ", LEAF_battery_Type, " ");
print_with_units(", Has heater: ", battery_HeatExist, " ");
print_with_units(", Max cell voltage: ", battery_min_max_voltage[1], "mV ");
print_with_units(", Min cell voltage: ", battery_min_max_voltage[0], "mV ");
#endif
// Update webserver datalayer
memcpy(datalayer_extended.nissanleaf.BatterySerialNumber, BatterySerialNumber, sizeof(BatterySerialNumber));
memcpy(datalayer_extended.nissanleaf.BatteryPartNumber, BatteryPartNumber, sizeof(BatteryPartNumber));
memcpy(datalayer_extended.nissanleaf.BMSIDcode, BMSIDcode, sizeof(BMSIDcode));
datalayer_extended.nissanleaf.LEAF_gen = LEAF_battery_Type;
datalayer_extended.nissanleaf.GIDS = battery_GIDS;
datalayer_extended.nissanleaf.ChargePowerLimit = battery_Charge_Power_Limit;
datalayer_extended.nissanleaf.MaxPowerForCharger = battery_MAX_POWER_FOR_CHARGER;
datalayer_extended.nissanleaf.Interlock = battery_Interlock;
datalayer_extended.nissanleaf.Insulation = battery_insulation;
datalayer_extended.nissanleaf.RelayCutRequest = battery_Relay_Cut_Request;
datalayer_extended.nissanleaf.FailsafeStatus = battery_Failsafe_Status;
datalayer_extended.nissanleaf.Full = battery_Full_CHARGE_flag;
datalayer_extended.nissanleaf.Empty = battery_Capacity_Empty;
datalayer_extended.nissanleaf.MainRelayOn = battery_MainRelayOn_flag;
datalayer_extended.nissanleaf.HeatExist = battery_HeatExist;
datalayer_extended.nissanleaf.HeatingStop = battery_Heating_Stop;
datalayer_extended.nissanleaf.HeatingStart = battery_Heating_Start;
datalayer_extended.nissanleaf.HeaterSendRequest = battery_Batt_Heater_Mail_Send_Request;
datalayer_extended.nissanleaf.CryptoChallenge = incomingChallenge;
datalayer_extended.nissanleaf.SolvedChallengeMSB =
((solvedChallenge[7] << 24) | (solvedChallenge[6] << 16) | (solvedChallenge[5] << 8) | solvedChallenge[4]);
datalayer_extended.nissanleaf.SolvedChallengeLSB =
((solvedChallenge[3] << 24) | (solvedChallenge[2] << 16) | (solvedChallenge[1] << 8) | solvedChallenge[0]);
datalayer_extended.nissanleaf.challengeFailed = challengeFailed;
// Update requests from webserver datalayer
if (datalayer_extended.nissanleaf.UserRequestSOHreset) {
stateMachineClearSOH = 0; //Start the statemachine
datalayer_extended.nissanleaf.UserRequestSOHreset = false;
}
}
#ifdef DOUBLE_BATTERY
@ -348,10 +380,6 @@ void update_values_battery2() { // Handle the values coming in from battery #2
datalayer.battery2.status.remaining_capacity_Wh = battery2_Wh_Remaining;
datalayer.battery2.status.active_power_W =
((battery2_Total_Voltage2 * battery2_Current2) /
4); //P = U * I (Both values are 0.5 per bit so the math is non-intuitive)
//Update temperature readings. Method depends on which generation LEAF battery is used
if (LEAF_battery2_Type == ZE0_BATTERY) {
//Since we only have average value, send the minimum as -1.0 degrees below average
@ -405,6 +433,12 @@ void update_values_battery2() { // Handle the values coming in from battery #2
clear_event(EVENT_BATTERY_EMPTY);
}
if (battery2_Total_Voltage2 == 0x3FF) { //Battery reports critical measurement unavailable
set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, 0);
} else {
clear_event(EVENT_BATTERY_VALUE_UNAVAILABLE);
}
if (battery2_Relay_Cut_Request) { //battery2_FAIL, BMS requesting shutdown and contactors to be opened
//Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario
datalayer.battery2.status.max_discharge_power_W = 0;
@ -487,11 +521,7 @@ void receive_can_battery2(CAN_frame rx_frame) {
battery2_Relay_Cut_Request = ((rx_frame.data.u8[1] & 0x18) >> 3);
battery2_Failsafe_Status = (rx_frame.data.u8[1] & 0x07);
battery2_MainRelayOn_flag = (bool)((rx_frame.data.u8[3] & 0x20) >> 5);
if (battery2_MainRelayOn_flag) {
datalayer.system.status.battery2_allows_contactor_closing = true;
} else {
datalayer.system.status.battery2_allows_contactor_closing = false;
}
//battery2_allows_contactor_closing written by check_interconnect_available();
battery2_Full_CHARGE_flag = (bool)((rx_frame.data.u8[3] & 0x10) >> 4);
battery2_Interlock = (bool)((rx_frame.data.u8[3] & 0x08) >> 3);
break;
@ -574,20 +604,16 @@ void receive_can_battery2(CAN_frame rx_frame) {
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
break;
case 0x7BB:
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
battery2_group_7bb = rx_frame.data.u8[3];
if (battery2_group_7bb != 1 && battery2_group_7bb != 2 &&
battery2_group_7bb != 4) { //We are only interested in groups 1,2 and 4
break;
}
}
if (stop_battery_query) { //Leafspy is active, stop our own polling
if (stop_battery_query) { //Leafspy/Service request is active, stop our own polling
break;
}
transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery_double);
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
battery2_group_7bb = rx_frame.data.u8[3];
transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery_double);
}
if (battery2_group_7bb == 1) //High precision SOC, Current, voltages etc.
{
@ -634,12 +660,6 @@ void receive_can_battery2(CAN_frame rx_frame) {
datalayer.battery2.status.cell_max_voltage_mV = battery2_min_max_voltage[1];
datalayer.battery2.status.cell_min_voltage_mV = battery2_min_max_voltage[0];
if (battery2_min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (battery2_min_max_voltage[0] <= MIN_CELL_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
break;
}
@ -826,18 +846,32 @@ void receive_can_battery(CAN_frame rx_frame) {
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
break;
case 0x7BB:
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
group_7bb = rx_frame.data.u8[3];
if (group_7bb != 1 && group_7bb != 2 && group_7bb != 4) { //We are only interested in groups 1,2 and 4
break;
// This section checks if we are doing a SOH reset towards BMS. If we do, all 7BB handling is halted
if (stateMachineClearSOH < 255) {
//Intercept the messages based on state machine
if (rx_frame.data.u8[0] == 0x06) { // Incoming challenge data!
// BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF
incomingChallenge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[4] << 16) | (rx_frame.data.u8[5] << 8) |
rx_frame.data.u8[6]);
}
//Error checking
if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F)) {
challengeFailed = true;
}
break;
}
if (stop_battery_query) { //Leafspy is active, stop our own polling
break;
}
transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery); //Request the next frame for the group
//First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
group_7bb = rx_frame.data.u8[3];
transmit_can(&LEAF_NEXT_LINE_REQUEST, can_config.battery); //Request the next frame for the group
}
if (group_7bb == 1) //High precision SOC, Current, voltages etc.
{
@ -884,12 +918,6 @@ void receive_can_battery(CAN_frame rx_frame) {
datalayer.battery.status.cell_max_voltage_mV = battery_min_max_voltage[1];
datalayer.battery.status.cell_min_voltage_mV = battery_min_max_voltage[0];
if (battery_min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (battery_min_max_voltage[0] <= MIN_CELL_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
break;
}
@ -963,6 +991,66 @@ void receive_can_battery(CAN_frame rx_frame) {
}
}
if (group_7bb == 0x83) //BatteryPartNumber
{
if (rx_frame.data.u8[0] == 0x10) { //First frame (101A6183334E4B32)
BatteryPartNumber[0] = rx_frame.data.u8[4];
BatteryPartNumber[1] = rx_frame.data.u8[5];
BatteryPartNumber[2] = rx_frame.data.u8[6];
BatteryPartNumber[3] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second frame (2141524205170000)
BatteryPartNumber[4] = rx_frame.data.u8[1];
BatteryPartNumber[5] = rx_frame.data.u8[2];
BatteryPartNumber[6] = rx_frame.data.u8[3];
}
if (rx_frame.data.u8[0] == 0x22) { //Third frame (2200000002101311)
}
if (rx_frame.data.u8[0] == 0x23) { //Fourth frame (23000000000080FF)
}
}
if (group_7bb == 0x84) { //BatterySerialNumber
if (rx_frame.data.u8[0] == 0x10) { //First frame (10 16 61 84 32 33 30 55)
BatterySerialNumber[0] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second frame (21 4B 31 31 39 32 45 30)
BatterySerialNumber[1] = rx_frame.data.u8[1];
BatterySerialNumber[2] = rx_frame.data.u8[2];
BatterySerialNumber[3] = rx_frame.data.u8[3];
BatterySerialNumber[4] = rx_frame.data.u8[4];
BatterySerialNumber[5] = rx_frame.data.u8[5];
BatterySerialNumber[6] = rx_frame.data.u8[6];
BatterySerialNumber[7] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x22) { //Third frame (22 30 31 34 38 32 20 A0)
BatterySerialNumber[8] = rx_frame.data.u8[1];
BatterySerialNumber[9] = rx_frame.data.u8[2];
BatterySerialNumber[10] = rx_frame.data.u8[3];
BatterySerialNumber[11] = rx_frame.data.u8[4];
BatterySerialNumber[12] = rx_frame.data.u8[5];
BatterySerialNumber[13] = rx_frame.data.u8[6];
BatterySerialNumber[14] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x23) { //Fourth frame (23 00 00 00 00 00 00 00)
}
}
if (group_7bb == 0x90) { //BMSIDcode
if (rx_frame.data.u8[0] == 0x10) { //First frame (100A619044434131)
BMSIDcode[0] = rx_frame.data.u8[4];
BMSIDcode[1] = rx_frame.data.u8[5];
BMSIDcode[2] = rx_frame.data.u8[6];
BMSIDcode[3] = rx_frame.data.u8[7];
}
if (rx_frame.data.u8[0] == 0x21) { //Second frame (2130303535FFFFFF)
BMSIDcode[4] = rx_frame.data.u8[1];
BMSIDcode[5] = rx_frame.data.u8[2];
BMSIDcode[6] = rx_frame.data.u8[3];
BMSIDcode[7] = rx_frame.data.u8[4];
}
}
break;
default:
break;
@ -1112,6 +1200,10 @@ void send_can_battery() {
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
if (stateMachineClearSOH < 255) { // Enter the ClearSOH statemachine only if we request it
clearSOH();
}
//When battery requests heating pack status change, ack this
if (battery_Batt_Heater_Mail_Send_Request) {
LEAF_50B.data.u8[6] = 0x20; //Batt_Heater_Mail_Send_OK
@ -1158,9 +1250,11 @@ void send_can_battery() {
//Every 10s, ask diagnostic data from the battery. Don't ask if someone is already polling on the bus (Leafspy?)
if (!stop_battery_query) {
group = (group == 1) ? 2 : (group == 2) ? 4 : 1;
// Cycle between group 1, 2, and 4 using ternary operation
LEAF_GROUP_REQUEST.data.u8[2] = group;
// Move to the next group
PIDindex = (PIDindex + 1) % 6; // 6 = amount of elements in the PIDgroups[]
LEAF_GROUP_REQUEST.data.u8[2] = PIDgroups[PIDindex];
transmit_can(&LEAF_GROUP_REQUEST, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&LEAF_GROUP_REQUEST, can_config.battery_double);
@ -1215,19 +1309,204 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib
return static_cast<uint16_t>(1094 + (309 - temperature) * 2.5714285714285715);
}
void clearSOH(void) {
stop_battery_query = true;
hold_off_with_polling_10seconds = 10; // Active battery polling is paused for 100 seconds
switch (stateMachineClearSOH) {
case 0: // Wait until polling actually stops
challengeFailed = false;
stateMachineClearSOH = 1;
break;
case 1: // Set CAN_PROCESS_FLAG to 0xC0
LEAF_CLEAR_SOH.data = {0x02, 0x10, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
// BMS should reply 02 50 C0 FF FF FF FF FF
stateMachineClearSOH = 2;
break;
case 2: // Set something ?
LEAF_CLEAR_SOH.data = {0x02, 0x3E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
// BMS should reply 7E FF FF FF FF FF FF
stateMachineClearSOH = 3;
break;
case 3: // Request challenge to solve
LEAF_CLEAR_SOH.data = {0x02, 0x27, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
// BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF
stateMachineClearSOH = 4;
break;
case 4: // Send back decoded challenge data
decodeChallengeData(incomingChallenge, solvedChallenge);
LEAF_CLEAR_SOH.data = {
0x10, 0x0A, 0x27, 0x66, solvedChallenge[0], solvedChallenge[1], solvedChallenge[2], solvedChallenge[3]};
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
// BMS should reply 7BB 8 30 01 00 FF FF FF FF FF // Proceed with more data (PID ACK)
stateMachineClearSOH = 5;
break;
case 5: // Reply with even more decoded challenge data
LEAF_CLEAR_SOH.data = {
0x21, solvedChallenge[4], solvedChallenge[5], solvedChallenge[6], solvedChallenge[7], 0x00, 0x00, 0x00};
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
// BMS should reply 02 67 66 FF FF FF FF FF // Thank you for the data
stateMachineClearSOH = 6;
break;
case 6: // Check if solved data was OK
LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
//7BB 8 03 71 03 01 FF FF FF FF // If all is well, BMS replies with 03 71 03 01.
//Incase you sent wrong challenge, you get 03 7f 31 12
stateMachineClearSOH = 7;
break;
case 7: // Reset SOH% request
LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00};
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
//7BB 8 03 71 03 02 FF FF FF FF // 03 71 03 02 means that BMS accepted command.
//7BB 03 7f 31 12 means your challenge was wrong, so command ignored
stateMachineClearSOH = 8;
break;
case 8: // Please proceed with resetting SOH
LEAF_CLEAR_SOH.data = {0x02, 0x10, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00};
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
// 7BB 8 02 50 81 FF FF FF FF FF // SOH reset OK
stateMachineClearSOH = 255;
break;
default:
break;
}
}
unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2) {
bool bVar1;
unsigned int uVar2, uVar3, uVar4, uVar5, uVar6, uVar7, uVar8, uVar9, uVar10, uVar11, iVar12;
param_1 = param_1 & 0xffff;
param_2 = param_2 & 0xffff;
uVar10 = 0xffff;
iVar12 = 2;
do {
uVar2 = param_2;
if ((param_1 & 1) == 1) {
uVar2 = param_1 >> 1;
}
uVar3 = param_2;
if ((param_1 >> 1 & 1) == 1) {
uVar3 = param_1 >> 2;
}
uVar4 = param_2;
if ((param_1 >> 2 & 1) == 1) {
uVar4 = param_1 >> 3;
}
uVar5 = param_2;
if ((param_1 >> 3 & 1) == 1) {
uVar5 = param_1 >> 4;
}
uVar6 = param_2;
if ((param_1 >> 4 & 1) == 1) {
uVar6 = param_1 >> 5;
}
uVar7 = param_2;
if ((param_1 >> 5 & 1) == 1) {
uVar7 = param_1 >> 6;
}
uVar11 = param_1 >> 7;
uVar8 = param_2;
if ((param_1 >> 6 & 1) == 1) {
uVar8 = uVar11;
}
param_1 = param_1 >> 8;
uVar9 = param_2;
if ((uVar11 & 1) == 1) {
uVar9 = param_1;
}
uVar10 =
(((((((((((((((uVar10 & 0x7fff) << 1 ^ uVar2) & 0x7fff) << 1 ^ uVar3) & 0x7fff) << 1 ^ uVar4) & 0x7fff) << 1 ^
uVar5) &
0x7fff)
<< 1 ^
uVar6) &
0x7fff)
<< 1 ^
uVar7) &
0x7fff)
<< 1 ^
uVar8) &
0x7fff)
<< 1 ^
uVar9;
bVar1 = iVar12 != 1;
iVar12 = iVar12 + -1;
} while (bVar1);
return uVar10;
}
unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3) {
return (param_3 ^ 0x7F88 | param_2 ^ 0x8FE7) * ((param_1 & 0xffff) >> 8 ^ param_1 & 0xff) & 0xffff;
}
short ShortMaskedSumAndProduct(short param_1, short param_2) {
unsigned short uVar1;
uVar1 = param_2 + param_1 * 0x0006 & 0xff;
return (uVar1 + param_1) * (uVar1 + param_2);
}
unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2) {
unsigned int uVar1;
param_1 = param_1 & 0xffff;
param_2 = param_2 & 0xffff;
uVar1 = param_2 & (param_1 | 0x0006) & 0xf;
return ((unsigned int)param_1 >> uVar1 | param_1 << (0x10 - uVar1 & 0x1f)) *
(param_2 << uVar1 | (unsigned int)param_2 >> (0x10 - uVar1 & 0x1f)) &
0xffff;
}
unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3) {
unsigned int uVar1, uVar2, iVar3, iVar4;
uVar1 = MaskedBitwiseRotateMultiply(param_2, param_3);
uVar2 = ShortMaskedSumAndProduct(param_2, param_3);
uVar1 = ComputeMaskedXorProduct(param_1, uVar1, uVar2);
uVar2 = ComputeMaskedXorProduct(param_1, uVar2, uVar1);
iVar3 = CyclicXorHash16Bit(uVar1, 0x8421);
iVar4 = CyclicXorHash16Bit(uVar2, 0x8421);
return iVar4 + iVar3 * 0x10000;
}
void decodeChallengeData(unsigned int incomingChallenge, unsigned char* solvedChallenge) {
unsigned int uVar1, uVar2;
uVar1 = CryptAlgo(0x54e9, 0x3afd, incomingChallenge >> 0x10);
uVar2 = CryptAlgo(incomingChallenge & 0xffff, incomingChallenge >> 0x10, 0x54e9);
*solvedChallenge = (unsigned char)uVar1;
solvedChallenge[1] = (unsigned char)uVar2;
solvedChallenge[2] = (unsigned char)((unsigned int)uVar2 >> 8);
solvedChallenge[3] = (unsigned char)((unsigned int)uVar1 >> 8);
solvedChallenge[4] = (unsigned char)((unsigned int)uVar2 >> 0x10);
solvedChallenge[5] = (unsigned char)((unsigned int)uVar1 >> 0x10);
solvedChallenge[6] = (unsigned char)((unsigned int)uVar2 >> 0x18);
solvedChallenge[7] = (unsigned char)((unsigned int)uVar1 >> 0x18);
return;
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Nissan LEAF battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = 4040; // 404.4V
datalayer.battery.info.min_design_voltage_dV = 2600; // 260.0V
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
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_mV = datalayer.battery.info.max_cell_voltage_mV;
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
#endif //DOUBLE_BATTERY
}

View file

@ -4,11 +4,23 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4040 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2600
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
uint16_t Temp_fromRAW_to_F(uint16_t temperature);
bool is_message_corrupt(CAN_frame rx_frame);
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
void clearSOH(void);
//Cryptographic functions
void decodeChallengeData(unsigned int SeedInput, unsigned char* Crypt_Output_Buffer);
unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2);
unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3);
short ShortMaskedSumAndProduct(short param_1, short param_2);
unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2);
unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3);
#endif

View file

@ -60,13 +60,13 @@ void update_values_battery() {
datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0) , invert the sign
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.max_charge_power_W = (max_charge_current * (voltage_dV / 10));
datalayer.battery.status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10));
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV;
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV;
@ -175,12 +175,12 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Pylon battery selected");
#endif
datalayer.battery.info.max_design_voltage_dV = 4040; // 404.0V, charging over this is not possible
datalayer.battery.info.min_design_voltage_dV = 3100; // 310.0V, under this, discharging further is disabled
strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
#endif

View file

@ -4,7 +4,13 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_CELL_DEVIATION_MV 9999
/* Change the following to suit your battery */
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 1500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 500
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);

View file

@ -0,0 +1,324 @@
#include "../include.h"
#ifdef RANGE_ROVER_PHEV_BATTERY
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "RANGE-ROVER-PHEV-BATTERY.h"
/* TODO
- LOG files from vehicle needed to determine CAN content needed to send towards battery!
- BCCM_PMZ_A (0x18B 50ms)
- BCCMB_PMZ_A (0x224 90ms)
- BCM_CCP_RX_PMZCAN (0x601 non cyclic)
- EPIC_PMZ_B (0x009 non cyclic)
- GWM_FuelPumpEnableDataControl_PMZ (0x1F8 non cyclic)
- GWM_IgnitionAuthDataTarget_PMZ (0x004 non cyclic)
- GWM_PMZ_A (0x008 10ms cyclic)
- GWM_PMZ_B -F, G-I, Immo, K-P
- 0x010 10ms
- 0x090 10ms
- 0x108 20ms
- 0x110 20ms
- 0x1d0 80ms
- 0x490 900ms
- 0x1B0 80ms
- 0x460 720ms
- 0x006 non cyclic immo
- 0x450 600ms
- 0x2b8 180ms
- 0x388 200ms
- 0x2b0 180ms
- 0x380 80ms
- GWM_PMZ_V_HYBRID (0x18d 60ms)
- HVAC_PMZ_A-E
- 0x1a8 70ms
- 0x210 100ms
- 0x300 200ms
- 0x440 180ms
- 0x0c0 10ms
- PCM_PMZ_C_Hybrid C, D, H, M
- 0x030 15ms
- 0x304 180ms
- 0x1C0 80ms
- 0x434 350ms
- TCU_PMZ_A
- 0x014 non cyclic, command from TCU, most likely not needed
- Determine CRC calculation
- Figure out contactor closing requirements
*/
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis50ms = 0; // will store last time a 50ms CAN Message was sent
//CAN content from battery
static bool StatusCAT5BPOChg = false;
static bool StatusCAT4Derate = false;
static uint8_t OCMonitorStatus = 0;
static bool StatusCAT3 = false;
static bool IsolationStatus = false;
static bool HVILStatus = false;
static bool ContactorStatus = false;
static uint8_t StatusGpCounter = 0;
static bool WeldCheckStatus = false;
static bool StatusCAT7NowBPO = false;
static bool StatusCAT6DlyBPO = false;
static uint8_t StatusGpCS = 0;
static uint8_t CAT6Count = 0;
static bool EndOfCharge = false;
static bool DerateWarning = false;
static bool PrechargeAllowed = false;
static uint8_t DischargeExtGpCounter = 0; // Counter 0-15
static uint8_t DischargeExtGpCS = 0; // CRC
static uint16_t DischargeVoltageLimit = 0; //Min voltage battery allows discharging to
static uint16_t DischargePowerLimitExt = 0; //Momentary Discharge power limit kW*0.01 (0-655)
static uint16_t DischargeContPwrLmt = 0; //Longterm Discharge power limit kW*0.01 (0-655)
static uint8_t PwrGpCS = 0; // CRC
static uint8_t PwrGpCounter = 0; // Counter 0-15
static uint16_t VoltageExt = 370; // Voltage of the HV Battery
static uint16_t VoltageBus = 0; // Voltage on the high-voltage DC bus
static int32_t CurrentExt =
209715; //Positive - discharge, Negative Charge (0 - 16777215) Scaling: 0.025 Offset: -209715.175 Units: Amps
static bool HVIsolationTestRunning = false;
static uint16_t VoltageOC =
0; //The instantaneous equivalent open-circuit voltage of the high voltage battery. This is used by the high-voltage inverter in power prediction and derating calculations.
static uint16_t DchCurrentLimit =
0; // A, 'Maximum current that can be delivered by the HV Battery during motoring mode i.e during discharging.
static uint16_t ChgCurrentLimit =
0; // - 1023 A, Maximum current that can be transferred into the HV Battery during generating mode i.e during charging. Charging is neagtive and discharging is positive.
static uint16_t ChargeContPwrLmt = 0; //Longterm charge power limit kW*0.01 (0-655)
static uint16_t ChargePowerLimitExt = 0; //Momentary Charge power limit kW*0.01 (0-655)
static uint8_t ChgExtGpCS = 0; // CRC
static uint8_t ChgExtGpCounter = 0; //counter 0-15
static uint16_t ChargeVoltageLimit = 500; //Max voltage limit during charging of the HV Battery.
static uint8_t CurrentWarning = 0; // 0 normal, 1 cell overcurrent, 2 cell undercurrent
static uint8_t TempWarning = 0; // 0 normal, 1 cell overtemp, 2 cell undertemp
static int8_t TempUpLimit = 0; //Upper temperature limit.
static uint8_t CellVoltWarning = 0; // 0 normal, 1 cell overvoltage, 2 cell undervoltage
static bool CCCVChargeMode = false; //0 CC, 1 = CV
static uint16_t CellVoltUpLimit = 0; //mV, Upper cell voltage limit
static uint16_t SOCHighestCell = 0; //0.01, %
static uint16_t SOCLowestCell = 0; //0.01, %
static uint16_t SOCAverage = 0; //0.01, %
static bool WakeUpTopUpReq =
false; //The HV Battery can trigger a vehicle wake-up to request its State of Charge to be increased.
static bool WakeUpThermalReq =
false; //The HV Battery can trigger a vehicle wake-up in order to be thermally managed (ie. cooled down OR warmed up).
static bool WakeUpDchReq =
false; //The HV Battery can trigger a vehicle wake-up to request its State of Charge to be reduced.
static uint16_t StateofHealth = 0;
static uint16_t EstimatedLossChg =
0; //fact0.001, kWh Expected energy which will be lost during charging (at the rate given by VSCEstChargePower) due to resistance within the HV Battery.
static bool CoolingRequest =
false; //HV Battery cooling request to be cooled by the eAC/chiller as its cooling needs exceed the LTR cooling loop capability.
static uint16_t EstimatedLossDch =
0; //fact0.001, kWh Expected energy which will be lost during discharging (at the rate given by VSCEstDischargePower) due to resistance within the HV Battery.
static uint8_t FanDutyRequest =
0; //Request from the HV Battery cooling system to demand a change of duty for the electrical engine cooling fan speed (whilst using its LTR cooling loop).
static bool ValveCtrlStat = false; //0 Chiller/Heater cooling loop requested , 1 LTR cooling loop requested
static uint16_t EstLossDchTgtSoC =
0; //fact0.001, kWh Expected energy which will be lost during discharging (at the rate given by VSCEstimatedDchPower) from the target charging SoC (PHEV: HVBattEnergyUsableMax, BEV: HVBattEnergyUsableBulk) down to HVBattEnergyUsableMin, due to resistance within the Traction Battery.
static uint8_t HeatPowerGenChg =
0; //fact0.1, kW, Estimated average heat generated by battery if charged at the rate given by VSCEstimatedChgPower.
static uint8_t HeatPowerGenDch =
0; //fact0.1, kW, Estimated average heat generated by battery if discharged at the rate given by VSCEstimatedDchPower.
static uint8_t WarmupRateChg =
0; //fact0.1, C/min , Expected average rate at which the battery will self-heat if charged at the rate given by VSCEstimatedChgPower.
static uint8_t WarmupRateDch =
0; //fact0.1, C/min , Expected average rate at which the battery will self-heat if discharged at the rate given by VSCEstimatedDchPower.
static uint16_t CellVoltageMax = 3700;
static uint16_t CellVoltageMin = 3700;
static int8_t CellTempAverage = 0; //factor0.5, -40 offset
static int8_t CellTempColdest = 0; //factor0.5, -40 offset
static int8_t CellTempHottest = 0; //factor0.5, -40 offset
static uint8_t HeaterCtrlStat = 0; //factor1, 0 offset
static bool ThermalOvercheck = false; // 0 OK, 1 NOT OK
static int8_t InletCoolantTemp = 0; //factor0.5, -40 offset
static bool ClntPumpDiagStat_UB = false;
static bool InletCoolantTemp_UB = false;
static bool CoolantLevel = false; // Coolant level OK , 1 NOT OK
static bool ClntPumpDiagStat = false; // 0 Pump OK, 1 NOT OK
static uint8_t MILRequest = 0; //No req, 1 ON, 2 FLASHING, 3 unused
static uint16_t EnergyAvailable = 0; //fac0.05 , The total energy available from the HV Battery
static uint16_t EnergyUsableMax = 0; //fac0.05 , The total energy available from the HV Battery at its maximum SOC
static uint16_t EnergyUsableMin = 0; //fac0.05 , The total energy available from the HV Battery at its minimum SOC
static uint16_t TotalCapacity =
0; //fac0.1 , Total Battery capacity in Kwh. This will reduce over the lifetime of the HV Battery.
//CAN messages needed by battery (LOG needed!)
CAN_frame RANGE_ROVER_18B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x18B, //CONTENT??? TODO
.data = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
void update_values_battery() {
datalayer.battery.status.real_soc = SOCAverage;
datalayer.battery.status.soh_pptt = StateofHealth * 10;
datalayer.battery.status.voltage_dV = VoltageExt * 10;
datalayer.battery.status.current_dA = (CurrentExt * 0.025) - 209715;
datalayer.battery.status.max_charge_power_W = (ChargeContPwrLmt * 10) - 6550;
datalayer.battery.status.max_discharge_power_W = (DischargeContPwrLmt * 10) - 6550;
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.cell_max_voltage_mV = CellVoltageMax;
datalayer.battery.status.cell_min_voltage_mV = CellVoltageMin;
datalayer.battery.status.temperature_min_dC = CellTempColdest * 10;
datalayer.battery.status.temperature_max_dC = CellTempHottest * 10;
datalayer.battery.info.max_design_voltage_dV = ChargeVoltageLimit * 10;
datalayer.battery.info.min_design_voltage_dV = DischargeVoltageLimit * 10;
}
void receive_can_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) {
case 0x080: // 15ms
StatusCAT5BPOChg = (rx_frame.data.u8[0] & 0x01);
StatusCAT4Derate = (rx_frame.data.u8[0] & 0x02) >> 1;
OCMonitorStatus = (rx_frame.data.u8[0] & 0x0C) >> 2;
StatusCAT3 = (rx_frame.data.u8[0] & 0x10) >> 4;
IsolationStatus = (rx_frame.data.u8[0] & 0x20) >> 5;
HVILStatus = (rx_frame.data.u8[0] & 0x40) >> 6;
ContactorStatus = (rx_frame.data.u8[0] & 0x80) >> 7;
StatusGpCounter = (rx_frame.data.u8[1] & 0x0F);
WeldCheckStatus = (rx_frame.data.u8[1] & 0x20) >> 5;
StatusCAT7NowBPO = (rx_frame.data.u8[1] & 0x40) >> 6;
StatusCAT6DlyBPO = (rx_frame.data.u8[1] & 0x80) >> 7;
StatusGpCS = rx_frame.data.u8[2];
CAT6Count = rx_frame.data.u8[3] & 0x7F;
EndOfCharge = (rx_frame.data.u8[6] & 0x04) >> 2;
DerateWarning = (rx_frame.data.u8[6] & 0x08) >> 3;
PrechargeAllowed = (rx_frame.data.u8[6] & 0x10) >> 4;
break;
case 0x100: // 20ms
DischargeExtGpCounter = (rx_frame.data.u8[0] & 0x0F);
DischargeExtGpCS = rx_frame.data.u8[1];
DischargeVoltageLimit = (((rx_frame.data.u8[2] & 0x03) << 8) | rx_frame.data.u8[3]);
DischargePowerLimitExt = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
DischargeContPwrLmt = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
break;
case 0x102: // 20ms
PwrGpCS = rx_frame.data.u8[0];
PwrGpCounter = (rx_frame.data.u8[1] & 0x3C) >> 2;
VoltageExt = (((rx_frame.data.u8[1] & 0x03) << 8) | rx_frame.data.u8[2]);
VoltageBus = (((rx_frame.data.u8[3] & 0x03) << 8) | rx_frame.data.u8[4]);
CurrentExt = ((rx_frame.data.u8[5] << 8) | (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7]);
break;
case 0x104: // 20ms
HVIsolationTestRunning = (rx_frame.data.u8[2] & 0x10) >> 4;
VoltageOC = (((rx_frame.data.u8[2] & 0x03) << 8) | rx_frame.data.u8[3]);
DchCurrentLimit = (((rx_frame.data.u8[4] & 0x03) << 8) | rx_frame.data.u8[5]);
ChgCurrentLimit = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]);
break;
case 0x10A: // 20ms
ChargeContPwrLmt = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]);
ChargePowerLimitExt = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
ChgExtGpCS = rx_frame.data.u8[4];
ChgExtGpCounter = (rx_frame.data.u8[5] >> 4);
ChargeVoltageLimit = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]);
break;
case 0x198: // 60ms
CurrentWarning = (rx_frame.data.u8[4] & 0x03);
TempWarning = ((rx_frame.data.u8[4] & 0x0C) >> 2);
TempUpLimit = (rx_frame.data.u8[5] / 2) - 40;
CellVoltWarning = ((rx_frame.data.u8[6] & 0x60) >> 5);
CCCVChargeMode = ((rx_frame.data.u8[6] & 0x80) >> 7);
CellVoltUpLimit = (((rx_frame.data.u8[6] & 0x1F) << 8) | rx_frame.data.u8[7]);
break;
case 0x220: // 100ms
SOCHighestCell = (((rx_frame.data.u8[0] & 0x3F) << 8) | rx_frame.data.u8[1]);
SOCLowestCell = (((rx_frame.data.u8[2] & 0x3F) << 8) | rx_frame.data.u8[3]);
SOCAverage = (((rx_frame.data.u8[4] & 0x3F) << 8) | rx_frame.data.u8[5]);
WakeUpTopUpReq = ((rx_frame.data.u8[6] & 0x04) >> 2);
WakeUpThermalReq = ((rx_frame.data.u8[6] & 0x08) >> 3);
WakeUpDchReq = ((rx_frame.data.u8[6] & 0x10) >> 4);
StateofHealth = (((rx_frame.data.u8[6] & 0x03) << 8) | rx_frame.data.u8[7]);
break;
case 0x308: // 190ms
EstimatedLossChg = (((rx_frame.data.u8[0] & 0x03) << 8) | rx_frame.data.u8[1]);
CoolingRequest = ((rx_frame.data.u8[6] & 0x04) >> 2);
EstimatedLossDch = (((rx_frame.data.u8[2] & 0x03) << 8) | rx_frame.data.u8[3]);
FanDutyRequest = (rx_frame.data.u8[4] & 0x7F);
ValveCtrlStat = ((rx_frame.data.u8[4] & 0x80) >> 7);
EstLossDchTgtSoC = (((rx_frame.data.u8[5] & 0x03) << 8) | rx_frame.data.u8[6]);
break;
case 0x424: // 280ms
HeatPowerGenChg = (rx_frame.data.u8[0] & 0x7F);
HeatPowerGenDch = (rx_frame.data.u8[1] & 0x7F);
WarmupRateChg = (rx_frame.data.u8[2] & 0x3F);
WarmupRateDch = (rx_frame.data.u8[3] & 0x3F);
CellVoltageMax = (((rx_frame.data.u8[4] & 0x1F) << 8) | rx_frame.data.u8[5]);
CellVoltageMin = (((rx_frame.data.u8[6] & 0x1F) << 8) | rx_frame.data.u8[7]);
break;
case 0x448: // 600ms
CellTempAverage = (rx_frame.data.u8[0] / 2) - 40;
CellTempColdest = (rx_frame.data.u8[1] / 2) - 40;
CellTempHottest = (rx_frame.data.u8[2] / 2) - 40;
HeaterCtrlStat = (rx_frame.data.u8[3] & 0x7F);
ThermalOvercheck = ((rx_frame.data.u8[3] & 0x80) >> 7);
InletCoolantTemp = rx_frame.data.u8[5];
ClntPumpDiagStat_UB = ((rx_frame.data.u8[6] & 0x04) >> 2);
InletCoolantTemp_UB = ((rx_frame.data.u8[6] & 0x08) >> 3);
CoolantLevel = ((rx_frame.data.u8[6] & 0x10) >> 4);
ClntPumpDiagStat = ((rx_frame.data.u8[6] & 0x20) >> 5);
MILRequest = ((rx_frame.data.u8[6] & 0xC0) >> 6);
break;
case 0x464: // 800ms
EnergyAvailable = (((rx_frame.data.u8[0] & 0x07) << 8) | rx_frame.data.u8[1]);
EnergyUsableMax = (((rx_frame.data.u8[2] & 0x07) << 8) | rx_frame.data.u8[3]);
EnergyUsableMin = (((rx_frame.data.u8[4] & 0x07) << 8) | rx_frame.data.u8[5]);
TotalCapacity = (((rx_frame.data.u8[6] & 0x0F) << 8) | rx_frame.data.u8[7]);
break;
case 0x5A2: //Not periodically transferred
break;
case 0x656: //Not periodically transferred
break;
case 0x657: //Not periodically transferred
break;
case 0x6C8: //Not periodically transferred
break;
case 0x6C9: //Not periodically transferred
break;
case 0x6CA: //Not periodically transferred
break;
case 0x6CB: //Not periodically transferred
break;
case 0x7EC: //Not periodically transferred
break;
default:
break;
}
}
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 50ms CAN Message
if (currentMillis - previousMillis50ms >= INTERVAL_50_MS) {
previousMillis50ms = currentMillis;
transmit_can(&RANGE_ROVER_18B, can_config.battery);
}
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
#endif //RANGE_ROVER_PHEV_BATTERY

View file

@ -0,0 +1,18 @@
#ifndef RANGE_ROVER_PHEV_BATTERY_H
#define RANGE_ROVER_PHEV_BATTERY_H
#include <Arduino.h>
#include "../include.h"
#define BATTERY_SELECTED
/* Change the following to suit your battery */
#define MAX_PACK_VOLTAGE_DV 5000 //TODO: Configure
#define MIN_PACK_VOLTAGE_DV 0 //TODO: Configure
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 500 //TODO: Configure
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
#endif

View file

@ -95,9 +95,6 @@ void update_values_battery() { //This function maps all the values fetched via
//The above value is 0 on some packs. We instead hardcode this now.
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_W;
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.temperature_min_dC = (LB_MIN_TEMPERATURE * 10);
datalayer.battery.status.temperature_max_dC = (LB_MAX_TEMPERATURE * 10);
@ -106,43 +103,36 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_max_voltage_mV = LB_Cell_Max_Voltage;
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, (LB_Cell_Max_Voltage / 20));
}
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, (LB_Cell_Min_Voltage / 20));
}
#ifdef DEBUG_LOG
logging.println("Values going to inverter:");
logging.print("SOH%: ");
logging.print(datalayer.battery.status.soh_pptt);
logging.print(", SOC% scaled: ");
logging.print(datalayer.battery.status.reported_soc);
logging.print(", Voltage: ");
logging.print(datalayer.battery.status.voltage_dV);
logging.print(", Max discharge power: ");
logging.print(datalayer.battery.status.max_discharge_power_W);
logging.print(", Max charge power: ");
logging.print(datalayer.battery.status.max_charge_power_W);
logging.print(", Max temp: ");
logging.print(datalayer.battery.status.temperature_max_dC);
logging.print(", Min temp: ");
logging.print(datalayer.battery.status.temperature_min_dC);
logging.print(", BMS Status (3=OK): ");
logging.print(datalayer.battery.status.bms_status);
#ifdef DEBUG_VIA_USB
Serial.println("Values going to inverter:");
Serial.print("SOH%: ");
Serial.print(datalayer.battery.status.soh_pptt);
Serial.print(", SOC% scaled: ");
Serial.print(datalayer.battery.status.reported_soc);
Serial.print(", Voltage: ");
Serial.print(datalayer.battery.status.voltage_dV);
Serial.print(", Max discharge power: ");
Serial.print(datalayer.battery.status.max_discharge_power_W);
Serial.print(", Max charge power: ");
Serial.print(datalayer.battery.status.max_charge_power_W);
Serial.print(", Max temp: ");
Serial.print(datalayer.battery.status.temperature_max_dC);
Serial.print(", Min temp: ");
Serial.print(datalayer.battery.status.temperature_min_dC);
Serial.print(", BMS Status (3=OK): ");
Serial.print(datalayer.battery.status.bms_status);
Serial.println("Battery values: ");
Serial.print("Real SOC: ");
Serial.print(LB_SOC);
Serial.print(", Current: ");
Serial.print(LB_Current);
Serial.print(", kWh remain: ");
Serial.print(LB_kWh_Remaining);
Serial.print(", max mV: ");
Serial.print(LB_Cell_Max_Voltage);
Serial.print(", min mV: ");
Serial.print(LB_Cell_Min_Voltage);
logging.println("Battery values: ");
logging.print("Real SOC: ");
logging.print(LB_SOC);
logging.print(", Current: ");
logging.print(LB_Current);
logging.print(", kWh remain: ");
logging.print(LB_kWh_Remaining);
logging.print(", max mV: ");
logging.print(LB_Cell_Max_Voltage);
logging.print(", min mV: ");
logging.print(LB_Cell_Min_Voltage);
#endif
}
@ -244,13 +234,15 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Renault Kangoo battery selected");
#endif
datalayer.battery.info.max_design_voltage_dV =
4040; // 404.0V, over this, charging is not possible (goes into forced discharge)
datalayer.battery.info.min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}
#endif

View file

@ -4,11 +4,12 @@
#include "../include.h"
#define BATTERY_SELECTED
#define ABSOLUTE_CELL_MAX_VOLTAGE 4150 // If cellvoltage goes over this mV, we go into FAULT mode
#define ABSOLUTE_CELL_MIN_VOLTAGE 2500 // If cellvoltage goes under this mV, we go into FAULT mode
#define MAX_CELL_DEVIATION_MV 500 // If cell mV delta exceeds this, we go into WARNING mode
#define MAX_CHARGE_POWER_W 5000 // Battery can be charged with this amount of power
#define MAX_PACK_VOLTAGE_DV 4150 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2500
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CHARGE_POWER_W 5000 // Battery can be charged with this amount of power
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);

View file

@ -0,0 +1,145 @@
#include <cstdint>
#include "../include.h"
#ifdef RENAULT_TWIZY_BATTERY
#include "../datalayer/datalayer.h"
#include "../devboard/utils/events.h"
#include "RENAULT-TWIZY.h"
/* Do not change code below unless you are sure what you are doing */
static int16_t cell_temperatures_dC[7] = {0};
static int16_t current_dA = 0;
static uint16_t voltage_dV = 0;
static int16_t cellvoltages_mV[14] = {0};
static int16_t max_discharge_power = 0;
static int16_t max_recup_power = 0;
static int16_t max_charge_power = 0;
static uint16_t SOC = 0;
static uint16_t SOH = 0;
static uint16_t remaining_capacity_Wh = 0;
int16_t max_value(int16_t* entries, size_t len) {
int result = INT16_MIN;
for (int i = 0; i < len; i++) {
if (entries[i] > result)
result = entries[i];
}
return result;
}
int16_t min_value(int16_t* entries, size_t len) {
int result = INT16_MAX;
for (int i = 0; i < len; i++) {
if (entries[i] < result)
result = entries[i];
}
return result;
}
void update_values_battery() {
datalayer.battery.status.real_soc = SOC;
datalayer.battery.status.soh_pptt = SOH;
datalayer.battery.status.voltage_dV = voltage_dV; //value is *10 (3700 = 370.0)
datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0)
datalayer.battery.status.remaining_capacity_Wh = remaining_capacity_Wh;
// The twizy provides two values: one for the maximum charge provided by the on-board charger
// and one for the maximum charge during recuperation.
// For now we use the lower of the two (usually the charger one)
datalayer.battery.status.max_charge_power_W = max_charge_power < max_recup_power ? max_charge_power : max_recup_power;
datalayer.battery.status.max_discharge_power_W = max_discharge_power;
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages_mV, sizeof(cellvoltages_mV));
datalayer.battery.status.cell_min_voltage_mV =
min_value(cellvoltages_mV, sizeof(cellvoltages_mV) / sizeof(*cellvoltages_mV));
datalayer.battery.status.cell_max_voltage_mV =
max_value(cellvoltages_mV, sizeof(cellvoltages_mV) / sizeof(*cellvoltages_mV));
datalayer.battery.status.temperature_min_dC =
min_value(cell_temperatures_dC, sizeof(cell_temperatures_dC) / sizeof(*cell_temperatures_dC));
datalayer.battery.status.temperature_max_dC =
max_value(cell_temperatures_dC, sizeof(cell_temperatures_dC) / sizeof(*cell_temperatures_dC));
}
void receive_can_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) {
case 0x155:
// max charge power is in steps of 300W from 0 to 7
max_charge_power = (uint16_t)rx_frame.data.u8[0] * 300;
// current is encoded as a 12 bit integer with Amps = value / 4 - 500
current_dA = (((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]) & 0xfff) * 10 / 4 - 5000;
// SOC is encoded as 16 bit integer with SOC% = value / 400
SOC = (((uint16_t)rx_frame.data.u8[4] << 8) | (uint16_t)rx_frame.data.u8[5]) / 4;
break;
case 0x424:
max_recup_power = rx_frame.data.u8[2] * 500;
max_discharge_power = rx_frame.data.u8[3] * 500;
SOH = (uint16_t)rx_frame.data.u8[5] * 100;
break;
case 0x425:
remaining_capacity_Wh = (uint16_t)rx_frame.data.u8[1] * 100;
break;
case 0x554:
for (int i = 0; i < 7; i++)
cell_temperatures_dC[i] = (int16_t)rx_frame.data.u8[i] * 10 - 400;
break;
case 0x556:
// cell voltages are 12 bit with V = value / 200
cellvoltages_mV[0] = (((int16_t)rx_frame.data.u8[0] << 4) | ((int16_t)rx_frame.data.u8[1] >> 4)) * 10 / 2;
cellvoltages_mV[1] = (((int16_t)(rx_frame.data.u8[1] & 0xf) << 8) | (int16_t)rx_frame.data.u8[2]) * 10 / 2;
cellvoltages_mV[2] = (((int16_t)rx_frame.data.u8[3] << 4) | ((int16_t)rx_frame.data.u8[4] >> 4)) * 10 / 2;
cellvoltages_mV[3] = (((int16_t)(rx_frame.data.u8[4] & 0xf) << 8) | (int16_t)rx_frame.data.u8[5]) * 10 / 2;
cellvoltages_mV[4] = (((int16_t)rx_frame.data.u8[6] << 4) | ((int16_t)rx_frame.data.u8[7] >> 4)) * 10 / 2;
break;
case 0x557:
// cell voltages are 12 bit with V = value / 200
cellvoltages_mV[5] = (((int16_t)rx_frame.data.u8[0] << 4) | ((int16_t)rx_frame.data.u8[1] >> 4)) * 10 / 2;
cellvoltages_mV[6] = (((int16_t)(rx_frame.data.u8[1] & 0xf) << 8) | (int16_t)rx_frame.data.u8[2]) * 10 / 2;
cellvoltages_mV[7] = (((int16_t)rx_frame.data.u8[3] << 4) | ((int16_t)rx_frame.data.u8[4] >> 4)) * 10 / 2;
cellvoltages_mV[8] = (((int16_t)(rx_frame.data.u8[4] & 0xf) << 8) | (int16_t)rx_frame.data.u8[5]) * 10 / 2;
cellvoltages_mV[9] = (((int16_t)rx_frame.data.u8[6] << 4) | ((int16_t)rx_frame.data.u8[7] >> 4)) * 10 / 2;
break;
case 0x55E:
// cell voltages are 12 bit with V = value / 200
cellvoltages_mV[10] = (((int16_t)rx_frame.data.u8[0] << 4) | ((int16_t)rx_frame.data.u8[1] >> 4)) * 10 / 2;
cellvoltages_mV[11] = (((int16_t)(rx_frame.data.u8[1] & 0xf) << 8) | (int16_t)rx_frame.data.u8[2]) * 10 / 2;
cellvoltages_mV[12] = (((int16_t)rx_frame.data.u8[3] << 4) | ((int16_t)rx_frame.data.u8[4] >> 4)) * 10 / 2;
cellvoltages_mV[13] = (((int16_t)(rx_frame.data.u8[4] & 0xf) << 8) | (int16_t)rx_frame.data.u8[5]) * 10 / 2;
// battery odometer in bytes 6 and 7
break;
case 0x55F:
// TODO: twizy has two pack voltages, assumingly the minimum and maximum measured.
// They usually only differ by 0.1V. We use the lower one here
// The other one is in the last 12 bit of the CAN packet
// pack voltage is encoded as 16 bit integer in dV
voltage_dV = (((int16_t)rx_frame.data.u8[5] << 4) | ((int16_t)rx_frame.data.u8[6] >> 4));
break;
default:
break;
}
}
void send_can_battery() {
// we do not need to send anything to the battery for now
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 14;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.total_capacity_Wh = 6600;
}
#endif

View file

@ -0,0 +1,12 @@
#ifndef RENAULT_TWIZY_BATTERY_H
#define RENAULT_TWIZY_BATTERY_H
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 579 // 57.9V at 100% SOC (with 70% SOH, new one might be higher)
#define MIN_PACK_VOLTAGE_DV 480 // 48.4V at 13.76% SOC
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_VOLTAGE_MV 4200 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 3400 //Battery is put into emergency stop if one cell goes below this value
#endif

View file

@ -6,18 +6,49 @@
/* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe/src/vehicle_renaultzoe.cpp
The Zoe BMS apparently does not send total pack voltage, so we use the polled 96x cellvoltages summed up as total voltage
Still TODO:
- Fix the missing cell96 issue (Only cells 1-95 is shown on cellmonitor page)
- Automatically detect if we are on 22 or 41kWh battery (Nice to have, requires log file from 22kWh battery)
/*
/* Do not change code below unless you are sure what you are doing */
static uint16_t LB_SOC = 50;
static uint16_t LB_Display_SOC = 50;
static uint16_t LB_SOH = 99;
static int16_t LB_Average_Temperature = 0;
static uint32_t LB_Charge_Power_W = 0;
static int32_t LB_Current = 0;
static uint32_t LB_Charging_Power_W = 0;
static uint32_t LB_Regen_allowed_W = 0;
static uint32_t LB_Discharge_allowed_W = 0;
static int16_t LB_Current = 0;
static int16_t LB_Cell_minimum_temperature = 0;
static int16_t LB_Cell_maximum_temperature = 0;
static uint16_t LB_kWh_Remaining = 0;
static uint16_t LB_Cell_Max_Voltage = 3700;
static uint16_t LB_Cell_Min_Voltage = 3700;
static uint16_t LB_Battery_Voltage = 3700;
static uint8_t LB_Heartbeat = 0;
static uint8_t frame0 = 0;
static uint8_t current_poll = 0;
static uint8_t requested_poll = 0;
static uint8_t group = 0;
static uint16_t cellvoltages[96];
static uint32_t calculated_total_pack_voltage_mV = 370000;
static uint8_t highbyte_cell_next_frame = 0;
static uint16_t SOC_polled = 50;
static int16_t cell_1_temperature_polled = 0;
static int16_t cell_2_temperature_polled = 0;
static int16_t cell_3_temperature_polled = 0;
static int16_t cell_4_temperature_polled = 0;
static int16_t cell_5_temperature_polled = 0;
static int16_t cell_6_temperature_polled = 0;
static int16_t cell_7_temperature_polled = 0;
static int16_t cell_8_temperature_polled = 0;
static int16_t cell_9_temperature_polled = 0;
static int16_t cell_10_temperature_polled = 0;
static int16_t cell_11_temperature_polled = 0;
static int16_t cell_12_temperature_polled = 0;
static uint16_t battery_mileage_in_km = 0;
static uint16_t kWh_from_beginning_of_battery_life = 0;
static bool looping_over_20 = false;
CAN_frame ZOE_423 = {.FD = false,
.ext_ID = false,
@ -27,72 +58,428 @@ CAN_frame ZOE_423 = {.FD = false,
CAN_frame ZOE_POLL_79B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B, //0x41 = cell bat module 1-62 , 0x42 = cell bat module 63-96
.ID = 0x79B,
.data = {0x02, 0x21, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame ZOE_ACK_79B = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x79B,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
static unsigned long previousMillis5000 = 0; // will store last time a 1000ms CAN Message was sent
#define GROUP1_CELLVOLTAGES_1_POLL 0x41
#define GROUP2_CELLVOLTAGES_2_POLL 0x42
#define GROUP3_METRICS 0x61
#define GROUP4_SOC 0x03
#define GROUP5_TEMPERATURE_POLL 0x04
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was sent
static unsigned long previousMillis250 = 0; // will store last time a 250ms CAN Message was sent
static uint8_t counter_423 = 0;
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.soh_pptt = (LB_SOH * 100); // Increase range from 99% -> 99.00%
datalayer.battery.status.real_soc = (LB_SOC * 10); // Increase LB_SOC range from 0-100.0 -> 100.00
datalayer.battery.status.real_soc = SOC_polled;
//datalayer.battery.status.real_soc = LB_Display_SOC; //Alternative would be to use Dash SOC%
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
datalayer.battery.status.current_dA = LB_Current; //TODO: Take from CAN
datalayer.battery.status.current_dA = LB_Current * 10; //Convert A to dA
//Calculate the remaining Wh amount from SOC% and max Wh value.
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.max_discharge_power_W = 5000; //TODO: Take from CAN
datalayer.battery.status.max_discharge_power_W = LB_Discharge_allowed_W;
datalayer.battery.status.max_charge_power_W = LB_Charge_Power_W;
datalayer.battery.status.max_charge_power_W = LB_Regen_allowed_W;
datalayer.battery.status.active_power_W;
int16_t temperatures[] = {cell_1_temperature_polled, cell_2_temperature_polled, cell_3_temperature_polled,
cell_4_temperature_polled, cell_5_temperature_polled, cell_6_temperature_polled,
cell_7_temperature_polled, cell_8_temperature_polled, cell_9_temperature_polled,
cell_10_temperature_polled, cell_11_temperature_polled, cell_12_temperature_polled};
datalayer.battery.status.temperature_min_dC = LB_Average_Temperature;
// Find the minimum and maximum temperatures
int16_t min_temperature = *std::min_element(temperatures, temperatures + 12);
int16_t max_temperature = *std::max_element(temperatures, temperatures + 12);
datalayer.battery.status.temperature_max_dC = LB_Average_Temperature;
datalayer.battery.status.temperature_min_dC = min_temperature * 10;
datalayer.battery.status.cell_min_voltage_mV;
datalayer.battery.status.temperature_max_dC = max_temperature * 10;
datalayer.battery.status.cell_max_voltage_mV;
//Map all cell voltages to the global array
memcpy(datalayer.battery.status.cell_voltages_mV, cellvoltages, 96 * sizeof(uint16_t));
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
// Initialize min and max, lets find which cells are min and max!
uint16_t min_cell_mv_value = std::numeric_limits<uint16_t>::max();
uint16_t max_cell_mv_value = 0;
calculated_total_pack_voltage_mV =
datalayer.battery.status.cell_voltages_mV
[0]; // cell96 issue, this value should be initialized to 0, but for now it is initialized to cell0
// Loop to find the min and max while ignoring zero values
for (uint8_t i = 0; i < datalayer.battery.info.number_of_cells; ++i) {
uint16_t voltage_mV = datalayer.battery.status.cell_voltages_mV[i];
calculated_total_pack_voltage_mV += voltage_mV;
if (voltage_mV != 0) { // Skip unread values (0)
min_cell_mv_value = std::min(min_cell_mv_value, voltage_mV);
max_cell_mv_value = std::max(max_cell_mv_value, voltage_mV);
}
}
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
// If all array values are 0, reset min/max to 3700
if (min_cell_mv_value == std::numeric_limits<uint16_t>::max()) {
min_cell_mv_value = 3700;
max_cell_mv_value = 3700;
calculated_total_pack_voltage_mV = 370000;
}
#ifdef DEBUG_VIA_USB
#endif
datalayer.battery.status.cell_min_voltage_mV = min_cell_mv_value;
datalayer.battery.status.cell_max_voltage_mV = max_cell_mv_value;
datalayer.battery.status.voltage_dV = static_cast<uint32_t>((calculated_total_pack_voltage_mV / 100)); // mV to dV
}
void receive_can_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) {
case 0x427:
LB_Charge_Power_W = rx_frame.data.u8[5] * 300;
case 0x155: //10ms - Charging power, current and SOC
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_Charging_Power_W = rx_frame.data.u8[0] * 300;
LB_Current = (((((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[2]) * 0.25) - 500);
LB_Display_SOC = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
break;
case 0x427: // NOTE: Not present on 41kWh battery!
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_kWh_Remaining = (((((rx_frame.data.u8[6] << 8) | (rx_frame.data.u8[7])) >> 6) & 0x3ff) * 0.1);
break;
case 0x42E: //HV SOC & Battery Temp & Charging Power
case 0x42E: //NOTE: Not present on 41kWh battery!
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_Battery_Voltage = (((((rx_frame.data.u8[3] << 8) | (rx_frame.data.u8[4])) >> 5) & 0x3ff) * 0.5); //0.5V/bit
LB_Average_Temperature = (((((rx_frame.data.u8[5] << 8) | (rx_frame.data.u8[6])) >> 5) & 0x7F) - 40);
break;
case 0x424: //100ms - Charge limits, Temperatures, SOH
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_Regen_allowed_W = rx_frame.data.u8[2] * 500;
LB_Discharge_allowed_W = rx_frame.data.u8[3] * 500;
LB_Cell_minimum_temperature = (rx_frame.data.u8[4] - 40);
LB_SOH = rx_frame.data.u8[5];
LB_Heartbeat = rx_frame.data.u8[6]; // Alternates between 0x55 and 0xAA every 500ms (Same as on Nissan LEAF)
LB_Cell_maximum_temperature = (rx_frame.data.u8[7] - 40);
break;
case 0x425: //100ms Unknown content
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery
break;
case 0x445: //100ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x4AE: //3000ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x4AF: //100ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x654: //SOC
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
LB_SOC = rx_frame.data.u8[3];
break;
case 0x658: //SOH
LB_SOH = (rx_frame.data.u8[4] & 0x7F);
case 0x658: //SOH - NOTE: Not present on 41kWh battery! (Is this message on 21kWh?)
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//LB_SOH = (rx_frame.data.u8[4] & 0x7F);
break;
case 0x659: //3000ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
//Sent only? by 41kWh battery (potential use for detecting which generation we are on)
break;
case 0x7BB: //Reply from active polling
// TODO: Handle the cellvoltages
frame0 = rx_frame.data.u8[0];
switch (frame0) {
case 0x10: //PID HEADER, datarow 0
requested_poll = rx_frame.data.u8[3];
transmit_can(&ZOE_ACK_79B, can_config.battery);
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[0] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[1] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[62] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[63] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (requested_poll == GROUP3_METRICS) {
//10,14,61,61,00,0A,8C,00,
}
if (requested_poll == GROUP4_SOC) {
//10,1D,61,03,01,94,1F,85, (1F85 = 8069 real SOC?)
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
//10,4D,61,04,09,12,3A,09,
cell_1_temperature_polled = (rx_frame.data.u8[6] - 40);
}
break;
case 0x21: //First datarow in PID group
if ((requested_poll == GROUP1_CELLVOLTAGES_1_POLL) && (looping_over_20 == false)) {
cellvoltages[2] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[3] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[4] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if ((requested_poll == GROUP1_CELLVOLTAGES_1_POLL) && (looping_over_20 == true)) {
cellvoltages[58] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[59] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[60] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[64] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[65] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[66] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP3_METRICS) {
//21,C8,C8,C8,C0,C0,00,00,
}
if (requested_poll == GROUP4_SOC) {
//21,21,32,00,00,00,00,01, (2132 = 8498 dash SOC?)
SOC_polled = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
cell_2_temperature_polled = (rx_frame.data.u8[2] - 40);
cell_3_temperature_polled = (rx_frame.data.u8[5] - 40);
//21,11,3A,09,14,3A,09,0D,
}
break;
case 0x22: //Second datarow in PID group
if ((requested_poll == GROUP1_CELLVOLTAGES_1_POLL) && (looping_over_20 == false)) {
cellvoltages[5] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[6] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[7] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[8] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if ((requested_poll == GROUP1_CELLVOLTAGES_1_POLL) && (looping_over_20 == true)) {
cellvoltages[61] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
looping_over_20 = false;
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[67] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[68] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[69] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[70] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (requested_poll == GROUP3_METRICS) {
//22,BB,7C,00,00,23,E4,FF, (BB7C = 47996km) (23E4 = 9188kWh)
battery_mileage_in_km = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
kWh_from_beginning_of_battery_life = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
}
if (requested_poll == GROUP4_SOC) {
//22,95,01,93,00,00,00,FF,
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
cell_4_temperature_polled = (rx_frame.data.u8[1] - 40);
cell_5_temperature_polled = (rx_frame.data.u8[4] - 40);
cell_6_temperature_polled = (rx_frame.data.u8[7] - 40);
//22,3A,08,F6,3B,08,EE,3B,
}
break;
case 0x23: //Third datarow in PID group
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[9] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[10] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[11] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[71] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[72] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[73] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP4_SOC) {
//23,FF,FF,FF,01,20,7B,00,
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
//23,08,AC,3D,08,C0,3C,09,
cell_7_temperature_polled = (rx_frame.data.u8[3] - 40);
cell_8_temperature_polled = (rx_frame.data.u8[6] - 40);
}
break;
case 0x24: //Fourth datarow in PID group
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[12] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[13] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[14] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[15] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[74] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[75] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[76] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[77] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (requested_poll == GROUP4_SOC) {
//24,00,00,00,00,00,00,00,
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
//24,03,3A,09,11,3A,09,19,
cell_9_temperature_polled = (rx_frame.data.u8[2] - 40);
cell_10_temperature_polled = (rx_frame.data.u8[5] - 40);
}
break;
case 0x25: //Fifth datarow in PID group
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[16] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[17] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[18] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[78] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[79] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[80] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
//25,3A,09,14,3A,FF,FF,FF,
cell_11_temperature_polled = (rx_frame.data.u8[1] - 40);
cell_12_temperature_polled = (rx_frame.data.u8[4] - 40);
}
break;
case 0x26:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[19] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[20] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[21] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[22] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[81] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[82] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[83] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[84] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
//26,FF,FF,FF,FF,FF,FF,FF,G
}
break;
case 0x27:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[23] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[24] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[25] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[85] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[86] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[87] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
//27,FF,FF,FF,FF,FF,FF,FF,
}
break;
case 0x28:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[26] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[27] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[28] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[29] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[88] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[89] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[90] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[91] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
//28,FF,FF,FF,FF,FF,FF,FF,
}
break;
case 0x29:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[30] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[21] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[32] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[92] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[93] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[94] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
//29,FF,FF,FF,FF,FF,FF,FF,
}
break;
case 0x2A:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[33] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[34] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[35] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[36] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
if (requested_poll == GROUP2_CELLVOLTAGES_2_POLL) {
cellvoltages[95] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
//2A,FF,FF,FF,FF,FF,3A,3A,
}
break;
case 0x2B:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[37] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[38] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[39] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
if (requested_poll == GROUP5_TEMPERATURE_POLL) {
//2B,3D,00,00,00,00,00,00,
}
break;
case 0x2C:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[40] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[41] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[42] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[43] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
break;
case 0x2D:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[44] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[45] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[46] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
break;
case 0x2E:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[47] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[48] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[49] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[50] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
}
break;
case 0x2F:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[51] = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
cellvoltages[52] = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
cellvoltages[53] = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
highbyte_cell_next_frame = rx_frame.data.u8[7];
}
break;
case 0x20:
if (requested_poll == GROUP1_CELLVOLTAGES_1_POLL) {
cellvoltages[54] = (highbyte_cell_next_frame << 8) | rx_frame.data.u8[1];
cellvoltages[55] = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
cellvoltages[56] = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
cellvoltages[57] = (rx_frame.data.u8[6] << 8) | rx_frame.data.u8[7];
looping_over_20 = true;
}
break;
default:
break;
}
break;
default:
break;
@ -119,20 +506,48 @@ void send_can_battery() {
}
counter_423 = (counter_423 + 1) % 10;
}
// 5000ms CAN handling
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis;
// 250ms CAN handling
if (currentMillis - previousMillis250 >= INTERVAL_250_MS) {
previousMillis250 = currentMillis;
switch (group) {
case 0:
current_poll = GROUP1_CELLVOLTAGES_1_POLL;
break;
case 1:
current_poll = GROUP2_CELLVOLTAGES_2_POLL;
break;
case 2:
current_poll = GROUP3_METRICS;
break;
case 3:
current_poll = GROUP4_SOC;
break;
case 4:
current_poll = GROUP5_TEMPERATURE_POLL;
break;
default:
break;
}
group = (group + 1) % 5; // Cycle 0-1-2-3-4-0-1...
ZOE_POLL_79B.data.u8[2] = current_poll;
transmit_can(&ZOE_POLL_79B, can_config.battery);
}
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Renault Zoe 22/40kWh battery selected");
#endif
datalayer.battery.info.max_design_voltage_dV = 4040;
datalayer.battery.info.min_design_voltage_dV = 2700;
strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen1 22/40kWh", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}
#endif

View file

@ -3,10 +3,12 @@
#include "../include.h"
#define BATTERY_SELECTED
#define ABSOLUTE_CELL_MAX_VOLTAGE 4100
#define ABSOLUTE_CELL_MIN_VOLTAGE 3000
#define MAX_CELL_DEVIATION_MV 500
#define MAX_PACK_VOLTAGE_DV 4200 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3000
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);

View file

@ -1,23 +1,69 @@
#include "../include.h"
#ifdef RENAULT_ZOE_GEN2_BATTERY
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
#include "../devboard/utils/events.h"
#include "RENAULT-ZOE-GEN2-BATTERY.h"
/* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component
/* TODO
- Add //NVROL Reset
- Add //Enable temporisation before sleep (see ljames28 repo)
"If the pack is in a state where it is confused about the time, you may need to reset it's NVROL memory.
However, if the power is later power cycled, it will revert back to his previous confused state.
Therefore, after resetting the NVROL you must enable "temporisation before sleep", and then stop streaming 373.
It will then save the data and go to sleep. When the pack is confused, the state of charge may reset back to incorrect value
every time the power is reset which can be dangerous. In this state, the voltage will still be accurate"
*/
/* Information in this file is based on:
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp
https://github.com/ljames28/Renault-Zoe-PH2-ZE50-Canbus-LBC-Information?tab=readme-ov-file
https://github.com/fesch/CanZE/tree/master/app/src/main/assets/ZOE_Ph2
/*
/* Do not change code below unless you are sure what you are doing */
static uint16_t LB_SOC = 50;
static uint16_t LB_SOH = 99;
static int16_t LB_Average_Temperature = 0;
static uint32_t LB_Charge_Power_W = 0;
static int32_t LB_Current = 0;
static uint16_t LB_kWh_Remaining = 0;
static uint16_t LB_Cell_Max_Voltage = 3700;
static uint16_t LB_Cell_Min_Voltage = 3700;
static uint16_t LB_Battery_Voltage = 3700;
static uint16_t battery_soc = 0;
static uint16_t battery_usable_soc = 5000;
static uint16_t battery_soh = 10000;
static uint16_t battery_pack_voltage = 370;
static uint16_t battery_max_cell_voltage = 3700;
static uint16_t battery_min_cell_voltage = 3700;
static uint16_t battery_12v = 0;
static uint16_t battery_avg_temp = 920;
static uint16_t battery_min_temp = 920;
static uint16_t battery_max_temp = 920;
static uint16_t battery_max_power = 0;
static uint16_t battery_interlock = 0;
static uint16_t battery_kwh = 0;
static int32_t battery_current = 32640;
static uint16_t battery_current_offset = 0;
static uint16_t battery_max_generated = 0;
static uint16_t battery_max_available = 0;
static uint16_t battery_current_voltage = 0;
static uint16_t battery_charging_status = 0;
static uint16_t battery_remaining_charge = 0;
static uint16_t battery_balance_capacity_total = 0;
static uint16_t battery_balance_time_total = 0;
static uint16_t battery_balance_capacity_sleep = 0;
static uint16_t battery_balance_time_sleep = 0;
static uint16_t battery_balance_capacity_wake = 0;
static uint16_t battery_balance_time_wake = 0;
static uint16_t battery_bms_state = 0;
static uint16_t battery_balance_switches = 0;
static uint16_t battery_energy_complete = 0;
static uint16_t battery_energy_partial = 0;
static uint16_t battery_slave_failures = 0;
static uint16_t battery_mileage = 0;
static uint16_t battery_fan_speed = 0;
static uint16_t battery_fan_period = 0;
static uint16_t battery_fan_control = 0;
static uint16_t battery_fan_duty = 0;
static uint16_t battery_temporisation = 0;
static uint16_t battery_time = 0;
static uint16_t battery_pack_time = 0;
static uint16_t battery_soc_min = 0;
static uint16_t battery_soc_max = 0;
CAN_frame ZOE_373 = {.FD = false,
.ext_ID = false,
@ -28,53 +74,284 @@ CAN_frame ZOE_POLL_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x03, 0x22, 0x90, 0x00, 0xff, 0xff, 0xff, 0xff}};
.data = {0x03, 0x22, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00}};
//NVROL Reset
CAN_frame ZOE_NVROL_1_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
CAN_frame ZOE_NVROL_2_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x04, 0x31, 0x01, 0xB0, 0x09, 0x00, 0xAA, 0xAA}};
//Enable temporisation before sleep
CAN_frame ZOE_SLEEP_1_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
CAN_frame ZOE_SLEEP_2_18DADBF1 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x18DADBF1,
.data = {0x04, 0x2E, 0x92, 0x81, 0x01, 0xAA, 0xAA, 0xAA}};
const uint16_t poll_commands[41] = {POLL_SOC,
POLL_USABLE_SOC,
POLL_SOH,
POLL_PACK_VOLTAGE,
POLL_MAX_CELL_VOLTAGE,
POLL_MIN_CELL_VOLTAGE,
POLL_12V,
POLL_AVG_TEMP,
POLL_MIN_TEMP,
POLL_MAX_TEMP,
POLL_MAX_POWER,
POLL_INTERLOCK,
POLL_KWH,
POLL_CURRENT,
POLL_CURRENT_OFFSET,
POLL_MAX_GENERATED,
POLL_MAX_AVAILABLE,
POLL_CURRENT_VOLTAGE,
POLL_CHARGING_STATUS,
POLL_REMAINING_CHARGE,
POLL_BALANCE_CAPACITY_TOTAL,
POLL_BALANCE_TIME_TOTAL,
POLL_BALANCE_CAPACITY_SLEEP,
POLL_BALANCE_TIME_SLEEP,
POLL_BALANCE_CAPACITY_WAKE,
POLL_BALANCE_TIME_WAKE,
POLL_BMS_STATE,
POLL_BALANCE_SWITCHES,
POLL_ENERGY_COMPLETE,
POLL_ENERGY_PARTIAL,
POLL_SLAVE_FAILURES,
POLL_MILEAGE,
POLL_FAN_SPEED,
POLL_FAN_PERIOD,
POLL_FAN_CONTROL,
POLL_FAN_DUTY,
POLL_TEMPORISATION,
POLL_TIME,
POLL_PACK_TIME,
POLL_SOC_MIN,
POLL_SOC_MAX};
static uint8_t poll_index = 0;
static uint16_t currentpoll = POLL_SOC;
static uint16_t reply_poll = 0;
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery.status.soh_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
datalayer.battery.status.soh_pptt = battery_soh;
datalayer.battery.status.real_soc = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
if (battery_soc >= 300) {
datalayer.battery.status.real_soc = battery_soc - 300;
} else {
datalayer.battery.status.real_soc = 0;
}
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
datalayer.battery.status.voltage_dV = battery_pack_voltage;
datalayer.battery.status.current_dA = LB_Current;
datalayer.battery.status.current_dA = ((battery_current - 32640) * 0.3125);
//Calculate the remaining Wh amount from SOC% and max Wh value.
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.max_discharge_power_W;
datalayer.battery.status.max_discharge_power_W = battery_max_available * 10;
datalayer.battery.status.max_charge_power_W;
datalayer.battery.status.max_charge_power_W = battery_max_generated * 10;
datalayer.battery.status.active_power_W;
datalayer.battery.status.temperature_min_dC = ((battery_min_temp - 640) * 0.625);
datalayer.battery.status.temperature_min_dC;
datalayer.battery.status.temperature_max_dC = ((battery_max_temp - 640) * 0.625);
datalayer.battery.status.temperature_max_dC;
datalayer.battery.status.cell_min_voltage_mV = (battery_min_cell_voltage * 0.976563);
datalayer.battery.status.cell_min_voltage_mV;
datalayer.battery.status.cell_max_voltage_mV = (battery_max_cell_voltage * 0.976563);
datalayer.battery.status.cell_max_voltage_mV;
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
#ifdef DEBUG_VIA_USB
#endif
// Update webserver datalayer
datalayer_extended.zoePH2.battery_soc = battery_soc;
datalayer_extended.zoePH2.battery_usable_soc = battery_usable_soc;
datalayer_extended.zoePH2.battery_soh = battery_soh;
datalayer_extended.zoePH2.battery_pack_voltage = battery_pack_voltage;
datalayer_extended.zoePH2.battery_max_cell_voltage = battery_max_cell_voltage;
datalayer_extended.zoePH2.battery_min_cell_voltage = battery_min_cell_voltage;
datalayer_extended.zoePH2.battery_12v = battery_12v;
datalayer_extended.zoePH2.battery_avg_temp = battery_avg_temp;
datalayer_extended.zoePH2.battery_min_temp = battery_min_temp;
datalayer_extended.zoePH2.battery_max_temp = battery_max_temp;
datalayer_extended.zoePH2.battery_max_power = battery_max_power;
datalayer_extended.zoePH2.battery_interlock = battery_interlock;
datalayer_extended.zoePH2.battery_kwh = battery_kwh;
datalayer_extended.zoePH2.battery_current = battery_current;
datalayer_extended.zoePH2.battery_current_offset = battery_current_offset;
datalayer_extended.zoePH2.battery_max_generated = battery_max_generated;
datalayer_extended.zoePH2.battery_max_available = battery_max_available;
datalayer_extended.zoePH2.battery_current_voltage = battery_current_voltage;
datalayer_extended.zoePH2.battery_charging_status = battery_charging_status;
datalayer_extended.zoePH2.battery_remaining_charge = battery_remaining_charge;
datalayer_extended.zoePH2.battery_balance_capacity_total = battery_balance_capacity_total;
datalayer_extended.zoePH2.battery_balance_time_total = battery_balance_time_total;
datalayer_extended.zoePH2.battery_balance_capacity_sleep = battery_balance_capacity_sleep;
datalayer_extended.zoePH2.battery_balance_time_sleep = battery_balance_time_sleep;
datalayer_extended.zoePH2.battery_balance_capacity_wake = battery_balance_capacity_wake;
datalayer_extended.zoePH2.battery_balance_time_wake = battery_balance_time_wake;
datalayer_extended.zoePH2.battery_bms_state = battery_bms_state;
datalayer_extended.zoePH2.battery_balance_switches = battery_balance_switches;
datalayer_extended.zoePH2.battery_energy_complete = battery_energy_complete;
datalayer_extended.zoePH2.battery_energy_partial = battery_energy_partial;
datalayer_extended.zoePH2.battery_slave_failures = battery_slave_failures;
datalayer_extended.zoePH2.battery_mileage = battery_mileage;
datalayer_extended.zoePH2.battery_fan_speed = battery_fan_speed;
datalayer_extended.zoePH2.battery_fan_period = battery_fan_period;
datalayer_extended.zoePH2.battery_fan_control = battery_fan_control;
datalayer_extended.zoePH2.battery_fan_duty = battery_fan_duty;
datalayer_extended.zoePH2.battery_temporisation = battery_temporisation;
datalayer_extended.zoePH2.battery_time = battery_time;
datalayer_extended.zoePH2.battery_pack_time = battery_pack_time;
datalayer_extended.zoePH2.battery_soc_min = battery_soc_min;
datalayer_extended.zoePH2.battery_soc_max = battery_soc_max;
}
void receive_can_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
switch (rx_frame.ID) {
case 0x18daf1db: // LBC Reply from active polling
case 0x18DAF1DB: // LBC Reply from active polling
//frame 2 & 3 contains
reply_poll = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
switch (reply_poll) {
case POLL_SOC:
battery_soc = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_USABLE_SOC:
battery_usable_soc = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_SOH:
battery_soh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_PACK_VOLTAGE:
battery_pack_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_MAX_CELL_VOLTAGE:
battery_max_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_MIN_CELL_VOLTAGE:
battery_min_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_12V:
battery_12v = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_AVG_TEMP:
battery_avg_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_MIN_TEMP:
battery_min_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_MAX_TEMP:
battery_max_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_MAX_POWER:
battery_max_power = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_INTERLOCK:
battery_interlock = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_KWH:
battery_kwh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CURRENT:
battery_current = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CURRENT_OFFSET:
battery_current_offset = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_MAX_GENERATED:
battery_max_generated = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_MAX_AVAILABLE:
battery_max_available = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CURRENT_VOLTAGE:
battery_current_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_CHARGING_STATUS:
battery_charging_status = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_REMAINING_CHARGE:
battery_remaining_charge = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_BALANCE_CAPACITY_TOTAL:
battery_balance_capacity_total = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_BALANCE_TIME_TOTAL:
battery_balance_time_total = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_BALANCE_CAPACITY_SLEEP:
battery_balance_capacity_sleep = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_BALANCE_TIME_SLEEP:
battery_balance_time_sleep = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_BALANCE_CAPACITY_WAKE:
battery_balance_capacity_wake = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_BALANCE_TIME_WAKE:
battery_balance_time_wake = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_BMS_STATE:
battery_bms_state = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_BALANCE_SWITCHES:
battery_balance_switches = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_ENERGY_COMPLETE:
battery_energy_complete = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_ENERGY_PARTIAL:
battery_energy_partial = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_SLAVE_FAILURES:
battery_slave_failures = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_MILEAGE:
battery_mileage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_FAN_SPEED:
battery_fan_speed = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_FAN_PERIOD:
battery_fan_period = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_FAN_CONTROL:
battery_fan_control = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_FAN_DUTY:
battery_fan_duty = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_TEMPORISATION:
battery_temporisation = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_TIME:
battery_time = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_PACK_TIME:
battery_pack_time = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_SOC_MIN:
battery_soc_min = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
case POLL_SOC_MAX:
battery_soc_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
break;
default: // Unknown reply
break;
}
break;
default:
break;
@ -90,18 +367,29 @@ void send_can_battery() {
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200));
}
previousMillis200 = currentMillis;
transmit_can(&ZOE_373, can_config.battery);
// Update current poll from the array
currentpoll = poll_commands[poll_index];
poll_index = (poll_index + 1) % 41;
ZOE_POLL_18DADBF1.data.u8[2] = (uint8_t)((currentpoll & 0xFF00) >> 8);
ZOE_POLL_18DADBF1.data.u8[3] = (uint8_t)(currentpoll & 0x00FF);
transmit_can(&ZOE_POLL_18DADBF1, can_config.battery);
transmit_can(&ZOE_373, can_config.battery);
}
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Renault Zoe 50kWh battery selected");
#endif
datalayer.battery.info.max_design_voltage_dV = 4040;
datalayer.battery.info.min_design_voltage_dV = 3100;
strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}
#endif

View file

@ -3,12 +3,55 @@
#include "../include.h"
#define BATTERY_SELECTED
#define ABSOLUTE_CELL_MAX_VOLTAGE 4100
#define ABSOLUTE_CELL_MIN_VOLTAGE 3000
#define MAX_PACK_VOLTAGE_DV 4100 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3000
#define MAX_CELL_DEVIATION_MV 500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
#define POLL_SOC 0x9001
#define POLL_USABLE_SOC 0x9002
#define POLL_SOH 0x9003
#define POLL_PACK_VOLTAGE 0x9005
#define POLL_MAX_CELL_VOLTAGE 0x9007
#define POLL_MIN_CELL_VOLTAGE 0x9009
#define POLL_12V 0x9011
#define POLL_AVG_TEMP 0x9012
#define POLL_MIN_TEMP 0x9013
#define POLL_MAX_TEMP 0x9014
#define POLL_MAX_POWER 0x9018
#define POLL_INTERLOCK 0x901A
#define POLL_KWH 0x91C8
#define POLL_CURRENT 0x925D
#define POLL_CURRENT_OFFSET 0x900C
#define POLL_MAX_GENERATED 0x900E
#define POLL_MAX_AVAILABLE 0x900F
#define POLL_CURRENT_VOLTAGE 0x9130
#define POLL_CHARGING_STATUS 0x9019
#define POLL_REMAINING_CHARGE 0xF45B
#define POLL_BALANCE_CAPACITY_TOTAL 0x924F
#define POLL_BALANCE_TIME_TOTAL 0x9250
#define POLL_BALANCE_CAPACITY_SLEEP 0x9251
#define POLL_BALANCE_TIME_SLEEP 0x9252
#define POLL_BALANCE_CAPACITY_WAKE 0x9262
#define POLL_BALANCE_TIME_WAKE 0x9263
#define POLL_BMS_STATE 0x9259
#define POLL_BALANCE_SWITCHES 0x912B
#define POLL_ENERGY_COMPLETE 0x9210
#define POLL_ENERGY_PARTIAL 0x9215
#define POLL_SLAVE_FAILURES 0x9129
#define POLL_MILEAGE 0x91CF
#define POLL_FAN_SPEED 0x912E
#define POLL_FAN_PERIOD 0x91F4
#define POLL_FAN_CONTROL 0x91C9
#define POLL_FAN_DUTY 0x91F5
#define POLL_TEMPORISATION 0x9281
#define POLL_TIME 0x9261
#define POLL_PACK_TIME 0x91C1
#define POLL_SOC_MIN 0x91B9
#define POLL_SOC_MAX 0x91BA
#endif

View file

@ -89,18 +89,29 @@ void update_values_battery() {
clear_event(EVENT_SOC_UNAVAILABLE);
}
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
datalayer.battery.status.soh_pptt; // This BMS does not have a SOH% formula
datalayer.battery.status.voltage_dV = total_voltage;
datalayer.battery.status.current_dA = total_current;
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
// Charge power is set in .h file
if (datalayer.battery.status.real_soc > 9900) {
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W;
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
datalayer.battery.status.max_charge_power_W =
MAX_CHARGE_POWER_ALLOWED_W *
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
} else { // No limits, max charging power allowed
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W;
}
datalayer.battery.status.max_charge_power_W = (protective_current * total_voltage) / 10;
datalayer.battery.status.max_discharge_power_W = (protective_current * total_voltage) / 10;
// Discharge power is also set in .h file
datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W;
uint16_t temperatures[] = {
module_1_temperature, module_2_temperature, module_3_temperature, module_4_temperature,
@ -151,17 +162,17 @@ void receive_can_battery(CAN_frame rx_frame) {
/*
// All CAN messages recieved will be logged via serial
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.ID, HEX);
Serial.print(" ");
Serial.print(rx_frame.DLC);
Serial.print(" ");
logging.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
logging.print(" ");
logging.print(rx_frame.ID, HEX);
logging.print(" ");
logging.print(rx_frame.DLC);
logging.print(" ");
for (int i = 0; i < rx_frame.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
logging.print(rx_frame.data.u8[i], HEX);
logging.print(" ");
}
Serial.println("");
logging.println("");
*/
switch (rx_frame.ID) {
case 0xF5: // This is the only message is sent from BMS
@ -495,6 +506,38 @@ void receive_can_battery(CAN_frame rx_frame) {
use_capacity_to_automatically_reset = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
low_temperature_protection_setting_value = (rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6];
protecting_historical_logs = rx_frame.data.u8[7];
if (protecting_historical_logs == 0x01) {
// Overcurrent protection
set_event(EVENT_DISCHARGE_LIMIT_EXCEEDED, 0); // could also be EVENT_CHARGE_LIMIT_EXCEEDED
} else if (protecting_historical_logs == 0x02) {
// over discharge protection
set_event(EVENT_BATTERY_UNDERVOLTAGE, 0);
} else if (protecting_historical_logs == 0x03) {
// overcharge protection
set_event(EVENT_BATTERY_OVERVOLTAGE, 0);
} else if (protecting_historical_logs == 0x04) {
// Over temperature protection
set_event(EVENT_BATTERY_OVERHEAT, 0);
} else if (protecting_historical_logs == 0x05) {
// Battery string error protection
set_event(EVENT_BATTERY_CAUTION, 0);
} else if (protecting_historical_logs == 0x06) {
// Damaged charging relay
set_event(EVENT_BATTERY_CHG_STOP_REQ, 0);
} else if (protecting_historical_logs == 0x07) {
// Damaged discharge relay
set_event(EVENT_BATTERY_DISCHG_STOP_REQ, 0);
} else if (protecting_historical_logs == 0x08) {
// Low voltage power outage protection
set_event(EVENT_12V_LOW, 0);
} else if (protecting_historical_logs == 0x09) {
// Voltage difference protection
set_event(EVENT_VOLTAGE_DIFFERENCE, differential_pressure_setting_value);
} else if (protecting_historical_logs == 0x0A) {
// Low temperature protection
set_event(EVENT_BATTERY_FROZEN, low_temperature_protection_setting_value);
}
} else if (mux == 0x54) {
hall_sensor_type = (rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2];
fan_start_setting_value = (rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4];
@ -527,12 +570,12 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("RJXZS BMS selected");
#endif
datalayer.battery.info.max_design_voltage_dV = 5000;
datalayer.battery.info.min_design_voltage_dV = 1000;
strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
#endif // RJXZS_BMS

View file

@ -3,8 +3,19 @@
#include <Arduino.h>
#include "../include.h"
#define BATTERY_SELECTED
/* Tweak these according to your battery build */
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 1500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_MV 250
#define MAX_DISCHARGE_POWER_ALLOWED_W 5000
#define MAX_CHARGE_POWER_ALLOWED_W 5000
#define MAX_CHARGE_POWER_WHEN_TOPBALANCING_W 500
#define RAMPDOWN_SOC 9000 // (90.00) SOC% to start ramping down from max charge power towards 0 at 100.00%
/* Do not modify any rows below*/
#define BATTERY_SELECTED
#define NATIVECAN_250KBPS
void setup_battery(void);

View file

@ -18,6 +18,8 @@ static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
static uint8_t poll_data_pid = 0;
static uint8_t counter_200 = 0;
static uint8_t checksum_200 = 0;
static uint16_t SOC_Display = 0;
static uint16_t batterySOH = 100;
@ -32,11 +34,27 @@ static int16_t leadAcidBatteryVoltage = 120;
static int8_t temperatureMax = 0;
static int8_t temperatureMin = 0;
static int16_t batteryAmps = 0;
static uint8_t counter_200 = 0;
static uint8_t checksum_200 = 0;
static uint8_t StatusBattery = 0;
static uint16_t cellvoltages_mv[96];
#ifdef DOUBLE_BATTERY
static uint16_t battery2_SOC_Display = 0;
static uint16_t battery2_SOH = 100;
static uint16_t battery2_CellVoltMax_mV = 3700;
static uint16_t battery2_CellVoltMin_mV = 3700;
static uint8_t battery2_CellVmaxNo = 0;
static uint8_t battery2_CellVminNo = 0;
static uint16_t battery2_allowedDischargePower = 0;
static uint16_t battery2_allowedChargePower = 0;
static uint16_t battery2_batteryVoltage = 0;
static int16_t battery2_leadAcidBatteryVoltage = 120;
static int8_t battery2_temperatureMax = 0;
static int8_t battery2_temperatureMin = 0;
static int16_t battery2_batteryAmps = 0;
static uint8_t battery2_StatusBattery = 0;
static uint16_t battery2_cellvoltages_mv[96];
#endif //DOUBLE_BATTERY
CAN_frame SANTAFE_200 = {.FD = false,
.ext_ID = false,
.DLC = 8,
@ -85,10 +103,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W = allowedChargePower * 10;
//Power in watts, Negative = charging batt
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.cell_max_voltage_mV = CellVoltMax_mV;
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
@ -100,10 +114,6 @@ void update_values_battery() { //This function maps all the values fetched via
if (leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
}
#ifdef DEBUG_VIA_USB
#endif
}
void receive_can_battery(CAN_frame rx_frame) {
@ -342,10 +352,13 @@ void send_can_battery() {
SANTAFE_200.data.u8[7] = checksum_200;
transmit_can(&SANTAFE_200, can_config.battery);
transmit_can(&SANTAFE_2A1, can_config.battery);
transmit_can(&SANTAFE_2F0, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&SANTAFE_200, can_config.battery_double);
transmit_can(&SANTAFE_2A1, can_config.battery_double);
transmit_can(&SANTAFE_2F0, can_config.battery_double);
#endif //DOUBLE_BATTERY
counter_200++;
if (counter_200 > 0xF) {
@ -358,36 +371,279 @@ void send_can_battery() {
previousMillis100 = currentMillis;
transmit_can(&SANTAFE_523, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&SANTAFE_523, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
// Send 500ms CAN Message
if (currentMillis - previousMillis500 >= INTERVAL_500_MS) {
previousMillis500 = currentMillis;
//PID data is polled after last message sent from battery:
if (poll_data_pid >= 5) { //polling one of 5 PIDs at 100ms, resolution = 500ms
poll_data_pid = 0;
}
poll_data_pid++;
if (poll_data_pid == 1) {
SANTAFE_7E4_poll.data.u8[3] = 0x01;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 2) {
SANTAFE_7E4_poll.data.u8[3] = 0x02;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 3) {
SANTAFE_7E4_poll.data.u8[3] = 0x03;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 4) {
SANTAFE_7E4_poll.data.u8[3] = 0x04;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
} else if (poll_data_pid == 5) {
SANTAFE_7E4_poll.data.u8[3] = 0x05;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
}
// PID data is polled after last message sent from battery:
poll_data_pid = (poll_data_pid % 5) + 1;
SANTAFE_7E4_poll.data.u8[3] = (uint8_t)poll_data_pid;
transmit_can(&SANTAFE_7E4_poll, can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&SANTAFE_7E4_poll, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
}
#ifdef DOUBLE_BATTERY
void update_values_battery2() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
datalayer.battery2.status.real_soc = (battery2_SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
datalayer.battery2.status.soh_pptt = (battery2_SOH * 100); //Increase decimals from 100% -> 100.00%
datalayer.battery2.status.voltage_dV = battery2_batteryVoltage;
datalayer.battery2.status.current_dA = -battery2_batteryAmps;
datalayer.battery2.status.remaining_capacity_Wh = static_cast<uint32_t>(
(static_cast<double>(datalayer.battery2.status.real_soc) / 10000) * datalayer.battery2.info.total_capacity_Wh);
datalayer.battery2.status.max_discharge_power_W = battery2_allowedDischargePower * 10;
datalayer.battery2.status.max_charge_power_W = battery2_allowedChargePower * 10;
//Power in watts, Negative = charging batt
datalayer.battery2.status.active_power_W =
((datalayer.battery2.status.voltage_dV * datalayer.battery2.status.current_dA) / 100);
datalayer.battery2.status.cell_max_voltage_mV = battery2_CellVoltMax_mV;
datalayer.battery2.status.cell_min_voltage_mV = battery2_CellVoltMin_mV;
datalayer.battery2.status.temperature_min_dC = battery2_temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery2.status.temperature_max_dC = battery2_temperatureMax * 10; //Increase decimals, 18C -> 18.0C
if (battery2_leadAcidBatteryVoltage < 110) {
set_event(EVENT_12V_LOW, battery2_leadAcidBatteryVoltage);
}
}
void receive_can_battery2(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x1FF:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_StatusBattery = (rx_frame.data.u8[0] & 0x0F);
break;
case 0x4D5:
break;
case 0x4DD:
break;
case 0x4DE:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x4E0:
break;
case 0x542:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_SOC_Display = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]) / 2;
break;
case 0x588:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_batteryVoltage = ((rx_frame.data.u8[1] << 8) + rx_frame.data.u8[0]);
break;
case 0x597:
break;
case 0x5A6:
break;
case 0x5A7:
break;
case 0x5AD:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_batteryAmps = (rx_frame.data.u8[3] << 8) + rx_frame.data.u8[2];
break;
case 0x5AE:
break;
case 0x5F1:
break;
case 0x620:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_leadAcidBatteryVoltage = rx_frame.data.u8[1];
battery2_temperatureMin = rx_frame.data.u8[6]; //Lowest temp in battery
battery2_temperatureMax = rx_frame.data.u8[7]; //Highest temp in battery
break;
case 0x670:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery2_allowedChargePower = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery2_allowedDischargePower = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
break;
case 0x671:
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
break;
case 0x7EC: //Data From polled PID group, BigEndian
switch (rx_frame.data.u8[0]) {
case 0x10: //"PID Header"
if (rx_frame.data.u8[4] == poll_data_pid) {
transmit_can(&SANTAFE_7E4_ack,
can_config.battery_double); //Send ack to BMS if the same frame is sent as polled
}
break;
case 0x21: //First frame in PID group
if (poll_data_pid == 1) {
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[0] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[1] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[2] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[3] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[4] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[5] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[32] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[33] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[34] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[35] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[36] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[37] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[64] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[65] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[66] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[67] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[68] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[69] = (rx_frame.data.u8[7] * 20);
}
break;
case 0x22: //Second datarow in PID group
if (poll_data_pid == 2) {
battery2_cellvoltages_mv[6] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[7] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[8] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[9] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[10] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[11] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[12] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[38] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[39] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[40] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[41] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[42] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[43] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[44] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[70] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[71] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[72] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[73] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[74] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[75] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[76] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 6) {
}
break;
case 0x23: //Third datarow in PID group
if (poll_data_pid == 1) {
battery2_CellVoltMax_mV = (rx_frame.data.u8[7] * 20); //(volts *50) *20 =mV
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[13] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[14] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[15] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[16] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[17] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[18] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[19] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[45] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[46] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[47] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[48] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[49] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[50] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[51] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[77] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[78] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[79] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[80] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[81] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[82] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[83] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
if (rx_frame.data.u8[6] > 0) {
battery2_SOH = rx_frame.data.u8[6];
}
if (battery2_SOH > 100) {
battery2_SOH = 100;
}
}
break;
case 0x24: //Fourth datarow in PID group
if (poll_data_pid == 1) {
battery2_CellVmaxNo = rx_frame.data.u8[1];
battery2_CellVminNo = rx_frame.data.u8[3];
CellVoltMin_mV = (rx_frame.data.u8[2] * 20); //(volts *50) *20 =mV
} else if (poll_data_pid == 2) {
battery2_cellvoltages_mv[20] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[21] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[22] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[23] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[24] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[25] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[26] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[52] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[53] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[54] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[55] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[56] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[57] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[58] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[84] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[85] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[86] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[87] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[88] = (rx_frame.data.u8[5] * 20);
battery2_cellvoltages_mv[89] = (rx_frame.data.u8[6] * 20);
battery2_cellvoltages_mv[90] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
}
break;
case 0x25: //Fifth datarow in PID group
if (poll_data_pid == 2) {
battery2_cellvoltages_mv[27] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[28] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[29] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[30] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[31] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 3) {
battery2_cellvoltages_mv[59] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[60] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[61] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[62] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[63] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 4) {
battery2_cellvoltages_mv[91] = (rx_frame.data.u8[1] * 20);
battery2_cellvoltages_mv[92] = (rx_frame.data.u8[2] * 20);
battery2_cellvoltages_mv[93] = (rx_frame.data.u8[3] * 20);
battery2_cellvoltages_mv[94] = (rx_frame.data.u8[4] * 20);
battery2_cellvoltages_mv[95] = (rx_frame.data.u8[5] * 20);
//Map all cell voltages to the global array, we have sampled them all!
memcpy(datalayer.battery2.status.cell_voltages_mV, battery2_cellvoltages_mv, 96 * sizeof(uint16_t));
} else if (poll_data_pid == 5) {
}
break;
case 0x26: //Sixth datarow in PID group
break;
case 0x27: //Seventh datarow in PID group
break;
case 0x28: //Eighth datarow in PID group
break;
}
break;
default:
break;
}
}
#endif //DOUBLE_BATTERY
uint8_t CalculateCRC8(CAN_frame rx_frame) {
int crc = 0;
@ -406,12 +662,23 @@ uint8_t CalculateCRC8(CAN_frame rx_frame) {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Hyundai Santa Fe PHEV battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = 4040;
datalayer.battery.info.min_design_voltage_dV = 2880;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = datalayer.battery.info.number_of_cells;
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_mV = datalayer.battery.info.max_cell_voltage_mV;
datalayer.battery2.info.min_cell_voltage_mV = datalayer.battery.info.min_cell_voltage_mV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
#endif //DOUBLE_BATTERY
}
#endif

View file

@ -4,7 +4,11 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4040 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2880
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
uint8_t CalculateCRC8(CAN_frame rx_frame);
void setup_battery(void);

View file

@ -94,8 +94,8 @@ void manageSerialLinkReceiver() {
bool readError = dataLinkReceive.checkReadError(true); // check for error & clear error flag
if (readError) {
Serial.print(currentTime);
Serial.println(" - ERROR: SerialDataLink - Read Error");
logging.print(currentTime);
logging.println(" - ERROR: SerialDataLink - Read Error");
lasterror = true;
errors++;
}
@ -112,8 +112,8 @@ void manageSerialLinkReceiver() {
//bms_status = ACTIVE; // just testing
if (lasterror) {
lasterror = false;
Serial.print(currentTime);
Serial.println(" - RECOVERY: SerialDataLink - Read GOOD");
logging.print(currentTime);
logging.println(" - RECOVERY: SerialDataLink - Read GOOD");
}
}
@ -134,34 +134,34 @@ void manageSerialLinkReceiver() {
// report Lost data & Max charge / Discharge reductions
if (minutesLost != last_minutesLost) {
last_minutesLost = minutesLost;
Serial.print(currentTime);
logging.print(currentTime);
if (batteryFault) {
Serial.print("Battery Fault (minutes) : ");
logging.print("Battery Fault (minutes) : ");
} else {
Serial.print(" - Minutes without data : ");
logging.print(" - Minutes without data : ");
}
Serial.print(minutesLost);
Serial.print(", max Charge = ");
Serial.print(datalayer.battery.status.max_charge_power_W);
Serial.print(", max Discharge = ");
Serial.println(datalayer.battery.status.max_discharge_power_W);
logging.print(minutesLost);
logging.print(", max Charge = ");
logging.print(datalayer.battery.status.max_charge_power_W);
logging.print(", max Discharge = ");
logging.println(datalayer.battery.status.max_discharge_power_W);
}
}
if (currentTime - reportTime > 59999) {
reportTime = currentTime;
Serial.print(currentTime);
Serial.print(" SerialDataLink-Receiver - NewData :");
Serial.print(reads);
Serial.print(" Errors : ");
Serial.println(errors);
logging.print(currentTime);
logging.print(" SerialDataLink-Receiver - NewData :");
logging.print(reads);
logging.print(" Errors : ");
logging.println(errors);
reads = 0;
errors = 0;
// --- printUsefullData();
//Serial.print("SOC = ");
//Serial.println(SOC);
#ifdef DEBUG_VIA_USB
//logging.print("SOC = ");
//logging.println(SOC);
#ifdef DEBUG_LOG
update_values_serial_link();
#endif
}
@ -179,47 +179,48 @@ void manageSerialLinkReceiver() {
}
void update_values_serial_link() {
Serial.println("Values from battery: ");
Serial.print("SOC: ");
Serial.print(datalayer.battery.status.real_soc);
Serial.print(" SOH: ");
Serial.print(datalayer.battery.status.soh_pptt);
Serial.print(" Voltage: ");
Serial.print(datalayer.battery.status.voltage_dV);
Serial.print(" Current: ");
Serial.print(datalayer.battery.status.current_dA);
Serial.print(" Capacity: ");
Serial.print(datalayer.battery.info.total_capacity_Wh);
Serial.print(" Remain cap: ");
Serial.print(datalayer.battery.status.remaining_capacity_Wh);
Serial.print(" Max discharge W: ");
Serial.print(datalayer.battery.status.max_discharge_power_W);
Serial.print(" Max charge W: ");
Serial.print(datalayer.battery.status.max_charge_power_W);
Serial.print(" BMS status: ");
Serial.print(datalayer.battery.status.bms_status);
Serial.print(" Power: ");
Serial.print(datalayer.battery.status.active_power_W);
Serial.print(" Temp min: ");
Serial.print(datalayer.battery.status.temperature_min_dC);
Serial.print(" Temp max: ");
Serial.print(datalayer.battery.status.temperature_max_dC);
Serial.print(" Cell max: ");
Serial.print(datalayer.battery.status.cell_max_voltage_mV);
Serial.print(" Cell min: ");
Serial.print(datalayer.battery.status.cell_min_voltage_mV);
Serial.print(" LFP : ");
Serial.print(datalayer.battery.info.chemistry);
Serial.print(" Battery Allows Contactor Closing: ");
Serial.print(datalayer.system.status.battery_allows_contactor_closing);
Serial.print(" Inverter Allows Contactor Closing: ");
Serial.print(datalayer.system.status.inverter_allows_contactor_closing);
logging.println("Values from battery: ");
logging.print("SOC: ");
logging.print(datalayer.battery.status.real_soc);
logging.print(" SOH: ");
logging.print(datalayer.battery.status.soh_pptt);
logging.print(" Voltage: ");
logging.print(datalayer.battery.status.voltage_dV);
logging.print(" Current: ");
logging.print(datalayer.battery.status.current_dA);
logging.print(" Capacity: ");
logging.print(datalayer.battery.info.total_capacity_Wh);
logging.print(" Remain cap: ");
logging.print(datalayer.battery.status.remaining_capacity_Wh);
logging.print(" Max discharge W: ");
logging.print(datalayer.battery.status.max_discharge_power_W);
logging.print(" Max charge W: ");
logging.print(datalayer.battery.status.max_charge_power_W);
logging.print(" BMS status: ");
logging.print(datalayer.battery.status.bms_status);
logging.print(" Power: ");
logging.print(datalayer.battery.status.active_power_W);
logging.print(" Temp min: ");
logging.print(datalayer.battery.status.temperature_min_dC);
logging.print(" Temp max: ");
logging.print(datalayer.battery.status.temperature_max_dC);
logging.print(" Cell max: ");
logging.print(datalayer.battery.status.cell_max_voltage_mV);
logging.print(" Cell min: ");
logging.print(datalayer.battery.status.cell_min_voltage_mV);
logging.print(" LFP : ");
logging.print(datalayer.battery.info.chemistry);
logging.print(" Battery Allows Contactor Closing: ");
logging.print(datalayer.system.status.battery_allows_contactor_closing);
logging.print(" Inverter Allows Contactor Closing: ");
logging.print(datalayer.system.status.inverter_allows_contactor_closing);
Serial.println("");
logging.println("");
}
void setup_battery(void) {
Serial.println("SERIAL_DATA_LINK_RECEIVER selected");
strncpy(datalayer.system.info.battery_protocol, "Serial link to another LilyGo board", 63);
datalayer.system.info.battery_protocol[63] = '\0';
}
// Needed to make the compiler happy
void update_values_battery() {}

View file

@ -4,9 +4,6 @@
#define SERIAL_LINK_RECEIVER_FROM_BATTERY_H
#define BATTERY_SELECTED
#ifndef MAX_CELL_DEVIATION_MV
#define MAX_CELL_DEVIATION_MV 9999
#endif
#include "../include.h"
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h"

File diff suppressed because it is too large Load diff

View file

@ -15,13 +15,20 @@
#define FLOAT_MAX_POWER_W 200 // W, what power to allow for top balancing battery
#define FLOAT_START_MV 20 // mV, how many mV under overvoltage to start float charging
#define MAX_PACK_VOLTAGE_SX_NCMA 4600 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_SX_NCMA 3100 // V+1, if pack voltage goes over this, charge stops
#define MAX_PACK_VOLTAGE_3Y_NCMA 4030 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_3Y_NCMA 3100 // V+1, if pack voltage goes below this, discharge stops
#define MAX_PACK_VOLTAGE_3Y_LFP 3880 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_3Y_LFP 2968 // V+1, if pack voltage goes below this, discharge stops
#define MAX_CELL_DEVIATION_MV 9999 // Handled inside the Tesla.cpp file, just for compilation
#define MAX_PACK_VOLTAGE_SX_NCMA 4600 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_SX_NCMA 3100 // V+1, if pack voltage goes over this, charge stops
#define MAX_PACK_VOLTAGE_3Y_NCMA 4030 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_3Y_NCMA 3100 // V+1, if pack voltage goes below this, discharge stops
#define MAX_PACK_VOLTAGE_3Y_LFP 3880 // V+1, if pack voltage goes over this, charge stops
#define MIN_PACK_VOLTAGE_3Y_LFP 2968 // V+1, if pack voltage goes below this, discharge stops
#define MAX_CELL_DEVIATION_NCA_NCM 500 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_DEVIATION_LFP 200 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_VOLTAGE_NCA_NCM 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_NCA_NCM 2950 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_VOLTAGE_LFP 3550 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_LFP 2800 //Battery is put into emergency stop if one cell goes below this value
//#define EXP_TESLA_BMS_DIGITAL_HVIL // Experimental parameter. Forces the transmission of additional CAN frames for experimental purposes, to test potential HVIL issues in 3/Y packs with newer firmware.
void printFaultCodesIfActive();
void printDebugIfActive(uint8_t symbol, const char* message);

View file

@ -15,9 +15,9 @@ CAN_frame TEST = {.FD = false,
.data = {0x10, 0x64, 0x00, 0xB0, 0x00, 0x1E, 0x00, 0x8F}};
void print_units(char* header, int value, char* units) {
Serial.print(header);
Serial.print(value);
Serial.print(units);
logging.print(header);
logging.print(value);
logging.print(units);
}
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
@ -38,8 +38,6 @@ void update_values_battery() { /* This function puts fake values onto the parame
datalayer.battery.status.cell_min_voltage_mV = 3500;
datalayer.battery.status.active_power_W = 0; // 0W
datalayer.battery.status.temperature_min_dC = 50; // 5.0*C
datalayer.battery.status.temperature_max_dC = 60; // 6.0*C
@ -49,15 +47,15 @@ void update_values_battery() { /* This function puts fake values onto the parame
datalayer.battery.status.max_charge_power_W = 5000; // 5kW
for (int i = 0; i < 97; ++i) {
datalayer.battery.status.cell_voltages_mV[i] = 3500 + i;
datalayer.battery.status.cell_voltages_mV[i] = 3700 + random(-20, 21);
}
//Fake that we get CAN messages
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB
Serial.println("FAKE Values going to inverter");
#ifdef DEBUG_LOG
logging.println("FAKE Values going to inverter");
print_units("SOH%: ", (datalayer.battery.status.soh_pptt * 0.01), "% ");
print_units(", SOC%: ", (datalayer.battery.status.reported_soc * 0.01), "% ");
print_units(", Voltage: ", (datalayer.battery.status.voltage_dV * 0.1), "V ");
@ -67,24 +65,70 @@ void update_values_battery() { /* This function puts fake values onto the parame
print_units(", Min temp: ", (datalayer.battery.status.temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage: ", datalayer.battery.status.cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage: ", datalayer.battery.status.cell_min_voltage_mV, "mV ");
Serial.println("");
logging.println("");
#endif
}
#ifdef DOUBLE_BATTERY
void update_values_battery2() { // Handle the values coming in from battery #2
datalayer.battery2.info.number_of_cells = 96;
datalayer.battery2.status.real_soc = 5000; // 50.00%
datalayer.battery2.status.soh_pptt = 9900; // 99.00%
//datalayer.battery.status.voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
datalayer.battery2.status.current_dA = 0; // 0 A
datalayer.battery2.info.total_capacity_Wh = 30000; // 30kWh
datalayer.battery2.status.remaining_capacity_Wh = 15000; // 15kWh
datalayer.battery2.status.cell_max_voltage_mV = 3596;
datalayer.battery2.status.cell_min_voltage_mV = 3500;
datalayer.battery2.status.temperature_min_dC = 50; // 5.0*C
datalayer.battery2.status.temperature_max_dC = 60; // 6.0*C
datalayer.battery2.status.max_discharge_power_W = 5000; // 5kW
datalayer.battery2.status.max_charge_power_W = 5000; // 5kW
for (int i = 0; i < 97; ++i) {
datalayer.battery2.status.cell_voltages_mV[i] = 3700 + random(-20, 21);
}
//Fake that we get CAN messages
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_LOG
logging.println("FAKE Values battery 2 going to inverter");
print_units("SOH 2 %: ", (datalayer.battery2.status.soh_pptt * 0.01), "% ");
print_units(", SOC 2 %: ", (datalayer.battery2.status.reported_soc * 0.01), "% ");
print_units(", Voltage 2: ", (datalayer.battery2.status.voltage_dV * 0.1), "V ");
print_units(", Max discharge power 2: ", datalayer.battery2.status.max_discharge_power_W, "W ");
print_units(", Max charge power 2: ", datalayer.battery2.status.max_charge_power_W, "W ");
print_units(", Max temp 2: ", (datalayer.battery2.status.temperature_max_dC * 0.1), "°C ");
print_units(", Min temp 2: ", (datalayer.battery2.status.temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage 2: ", datalayer.battery2.status.cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage 2: ", datalayer.battery2.status.cell_min_voltage_mV, "mV ");
logging.println("");
#endif
}
void receive_can_battery2(CAN_frame rx_frame) {
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
}
#endif // DOUBLE_BATTERY
void receive_can_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
// All CAN messages recieved will be logged via serial
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.ID, HEX);
Serial.print(" ");
Serial.print(rx_frame.DLC);
Serial.print(" ");
for (int i = 0; i < rx_frame.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
}
Serial.println("");
}
void send_can_battery() {
unsigned long currentMillis = millis();
@ -92,18 +136,21 @@ void send_can_battery() {
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis;
// Put fake messages here incase you want to test sending CAN
transmit_can(&TEST, can_config.battery);
//transmit_can(&TEST, can_config.battery);
}
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Test mode with fake battery selected");
#endif
randomSeed(analogRead(0));
strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV =
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
datalayer.battery.info.min_design_voltage_dV = 2450; // 245.0V under this, discharging further is disabled
datalayer.battery.info.number_of_cells = 96;
datalayer.system.status.battery_allows_contactor_closing = true;
}
#endif

View file

@ -8,9 +8,6 @@
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
#define MAX_CELL_VOLTAGE 4210 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
static float BATT_U = 0; //0x3A
static float MAX_U = 0; //0x3A
static float MIN_U = 0; //0x3A
@ -22,8 +19,8 @@ static float BATT_T_MIN = 0; //0x413
static float BATT_T_AVG = 0; //0x413
static uint16_t SOC_BMS = 0; //0X37D
static uint16_t SOC_CALC = 0;
static uint16_t CELL_U_MAX = 0; //0x37D
static uint16_t CELL_U_MIN = 0; //0x37D
static uint16_t CELL_U_MAX = 3700; //0x37D
static uint16_t CELL_U_MIN = 3700; //0x37D
static uint8_t CELL_ID_U_MAX = 0; //0x37D
static uint16_t HvBattPwrLimDchaSoft = 0; //0x369
static uint8_t batteryModuleNumber = 0x10; // First battery module
@ -86,7 +83,6 @@ void update_values_battery() { //This function maps all the values fetched via
//datalayer.battery.status.max_discharge_power_W = HvBattPwrLimDchaSoft * 1000; // Use power limit reported from BMS, not trusted ATM
datalayer.battery.status.max_discharge_power_W = 30000;
datalayer.battery.status.max_charge_power_W = 30000;
datalayer.battery.status.active_power_W = (BATT_U)*BATT_I;
datalayer.battery.status.temperature_min_dC = BATT_T_MIN;
datalayer.battery.status.temperature_max_dC = BATT_T_MAX;
@ -98,49 +94,49 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
}
#ifdef DEBUG_VIA_USB
Serial.print("BMS reported SOC%: ");
Serial.println(SOC_BMS);
Serial.print("Calculated SOC%: ");
Serial.println(SOC_CALC);
Serial.print("Rescaled SOC%: ");
Serial.println(datalayer.battery.status.reported_soc / 100);
Serial.print("Battery current: ");
Serial.println(BATT_I);
Serial.print("Battery voltage: ");
Serial.println(BATT_U);
Serial.print("Battery maximum voltage limit: ");
Serial.println(MAX_U);
Serial.print("Battery minimum voltage limit: ");
Serial.println(MIN_U);
Serial.print("Remaining Energy: ");
Serial.println(remaining_capacity);
Serial.print("Discharge limit: ");
Serial.println(HvBattPwrLimDchaSoft);
Serial.print("Battery Error Indication: ");
Serial.println(BATT_ERR_INDICATION);
Serial.print("Maximum battery temperature: ");
Serial.println(BATT_T_MAX / 10);
Serial.print("Minimum battery temperature: ");
Serial.println(BATT_T_MIN / 10);
Serial.print("Average battery temperature: ");
Serial.println(BATT_T_AVG / 10);
Serial.print("BMS Highest cell voltage: ");
Serial.println(CELL_U_MAX * 10);
Serial.print("BMS Lowest cell voltage: ");
Serial.println(CELL_U_MIN * 10);
Serial.print("BMS Highest cell nr: ");
Serial.println(CELL_ID_U_MAX);
Serial.print("Highest cell voltage: ");
Serial.println(min_max_voltage[1]);
Serial.print("Lowest cell voltage: ");
Serial.println(min_max_voltage[0]);
Serial.print("Cell voltage,");
#ifdef DEBUG_LOG
logging.print("BMS reported SOC%: ");
logging.println(SOC_BMS);
logging.print("Calculated SOC%: ");
logging.println(SOC_CALC);
logging.print("Rescaled SOC%: ");
logging.println(datalayer.battery.status.reported_soc / 100);
logging.print("Battery current: ");
logging.println(BATT_I);
logging.print("Battery voltage: ");
logging.println(BATT_U);
logging.print("Battery maximum voltage limit: ");
logging.println(MAX_U);
logging.print("Battery minimum voltage limit: ");
logging.println(MIN_U);
logging.print("Remaining Energy: ");
logging.println(remaining_capacity);
logging.print("Discharge limit: ");
logging.println(HvBattPwrLimDchaSoft);
logging.print("Battery Error Indication: ");
logging.println(BATT_ERR_INDICATION);
logging.print("Maximum battery temperature: ");
logging.println(BATT_T_MAX / 10);
logging.print("Minimum battery temperature: ");
logging.println(BATT_T_MIN / 10);
logging.print("Average battery temperature: ");
logging.println(BATT_T_AVG / 10);
logging.print("BMS Highest cell voltage: ");
logging.println(CELL_U_MAX * 10);
logging.print("BMS Lowest cell voltage: ");
logging.println(CELL_U_MIN * 10);
logging.print("BMS Highest cell nr: ");
logging.println(CELL_ID_U_MAX);
logging.print("Highest cell voltage: ");
logging.println(min_max_voltage[1]);
logging.print("Lowest cell voltage: ");
logging.println(min_max_voltage[0]);
logging.print("Cell voltage,");
while (cnt < 108) {
Serial.print(cell_voltages[cnt++]);
Serial.print(",");
logging.print(cell_voltages[cnt++]);
logging.print(",");
}
Serial.println(";");
logging.println(";");
#endif
}
@ -152,8 +148,8 @@ void receive_can_battery(CAN_frame rx_frame) {
BATT_I = (0 - ((((rx_frame.data.u8[6] & 0x7F) * 256.0 + rx_frame.data.u8[7]) * 0.1) - 1638));
else {
BATT_I = 0;
#ifdef DEBUG_VIA_USB
Serial.println("BATT_I not valid");
#ifdef DEBUG_LOG
logging.println("BATT_I not valid");
#endif
}
@ -161,22 +157,22 @@ void receive_can_battery(CAN_frame rx_frame) {
MAX_U = (((rx_frame.data.u8[2] & 0x07) * 256.0 + rx_frame.data.u8[3]) * 0.25);
else {
//MAX_U = 0;
//Serial.println("MAX_U not valid"); // Value toggles between true/false from BMS
//logging.println("MAX_U not valid"); // Value toggles between true/false from BMS
}
if ((rx_frame.data.u8[4] & 0x08) == 0x08)
MIN_U = (((rx_frame.data.u8[4] & 0x07) * 256.0 + rx_frame.data.u8[5]) * 0.25);
else {
//MIN_U = 0;
//Serial.println("MIN_U not valid"); // Value toggles between true/false from BMS
//logging.println("MIN_U not valid"); // Value toggles between true/false from BMS
}
if ((rx_frame.data.u8[0] & 0x08) == 0x08)
BATT_U = (((rx_frame.data.u8[0] & 0x07) * 256.0 + rx_frame.data.u8[1]) * 0.25);
else {
BATT_U = 0;
#ifdef DEBUG_VIA_USB
Serial.println("BATT_U not valid");
#ifdef DEBUG_LOG
logging.println("BATT_U not valid");
#endif
}
break;
@ -193,8 +189,8 @@ void receive_can_battery(CAN_frame rx_frame) {
BATT_ERR_INDICATION = ((rx_frame.data.u8[0] & 0x40) >> 6);
else {
BATT_ERR_INDICATION = 0;
#ifdef DEBUG_VIA_USB
Serial.println("BATT_ERR_INDICATION not valid");
#ifdef DEBUG_LOG
logging.println("BATT_ERR_INDICATION not valid");
#endif
}
if ((rx_frame.data.u8[0] & 0x20) == 0x20) {
@ -205,8 +201,8 @@ void receive_can_battery(CAN_frame rx_frame) {
BATT_T_MAX = 0;
BATT_T_MIN = 0;
BATT_T_AVG = 0;
#ifdef DEBUG_VIA_USB
Serial.println("BATT_T not valid");
#ifdef DEBUG_LOG
logging.println("BATT_T not valid");
#endif
}
break;
@ -215,8 +211,8 @@ void receive_can_battery(CAN_frame rx_frame) {
HvBattPwrLimDchaSoft = (((rx_frame.data.u8[6] & 0x03) * 256 + rx_frame.data.u8[6]) >> 2);
} else {
HvBattPwrLimDchaSoft = 0;
#ifdef DEBUG_VIA_USB
Serial.println("HvBattPwrLimDchaSoft not valid");
#ifdef DEBUG_LOG
logging.println("HvBattPwrLimDchaSoft not valid");
#endif
}
break;
@ -225,8 +221,8 @@ void receive_can_battery(CAN_frame rx_frame) {
SOC_BMS = ((rx_frame.data.u8[6] & 0x03) * 256 + rx_frame.data.u8[7]);
} else {
SOC_BMS = 0;
#ifdef DEBUG_VIA_USB
Serial.println("SOC_BMS not valid");
#ifdef DEBUG_LOG
logging.println("SOC_BMS not valid");
#endif
}
@ -234,8 +230,8 @@ void receive_can_battery(CAN_frame rx_frame) {
CELL_U_MAX = ((rx_frame.data.u8[2] & 0x01) * 256 + rx_frame.data.u8[3]);
else {
CELL_U_MAX = 0;
#ifdef DEBUG_VIA_USB
Serial.println("CELL_U_MAX not valid");
#ifdef DEBUG_LOG
logging.println("CELL_U_MAX not valid");
#endif
}
@ -243,8 +239,8 @@ void receive_can_battery(CAN_frame rx_frame) {
CELL_U_MIN = ((rx_frame.data.u8[0] & 0x01) * 256.0 + rx_frame.data.u8[1]);
else {
CELL_U_MIN = 0;
#ifdef DEBUG_VIA_USB
Serial.println("CELL_U_MIN not valid");
#ifdef DEBUG_LOG
logging.println("CELL_U_MIN not valid");
#endif
}
@ -252,8 +248,8 @@ void receive_can_battery(CAN_frame rx_frame) {
CELL_ID_U_MAX = ((rx_frame.data.u8[4] & 0x01) * 256.0 + rx_frame.data.u8[5]);
else {
CELL_ID_U_MAX = 0;
#ifdef DEBUG_VIA_USB
Serial.println("CELL_ID_U_MAX not valid");
#ifdef DEBUG_LOG
logging.println("CELL_ID_U_MAX not valid");
#endif
}
break;
@ -288,12 +284,6 @@ void receive_can_battery(CAN_frame rx_frame) {
min_max_voltage[1] = cell_voltages[cellcounter];
}
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (min_max_voltage[0] <= MIN_CELL_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
transmit_can(&VOLVO_SOH_Req, can_config.battery); //Send SOH read request
}
rxConsecutiveFrames = 0;
@ -342,13 +332,13 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Volvo SPA XC40 Recharge / Polestar2 78kWh battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 78kWh battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 108;
datalayer.battery.info.max_design_voltage_dV =
4540; // 454.0V, over this, charging is not possible (goes into forced discharge)
datalayer.battery.info.min_design_voltage_dV = 2938; // 293.8V under this, discharging further is disabled
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
}
#endif

View file

@ -4,7 +4,11 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4540 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2938
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4210 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);

View file

@ -101,8 +101,8 @@ void receive_can_charger(CAN_frame rx_frame) {
case 0x308:
break;
default:
#ifdef DEBUG_VIA_USB
Serial.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.MsgID);
#ifdef DEBUG_LOG
logging.printf("CAN Rcv unknown frame MsgID=%x\n", rx_frame.MsgID);
#endif
break;
}
@ -177,15 +177,15 @@ void send_can_charger() {
transmit_can(&charger_set_targets, can_config.charger);
}
#ifdef DEBUG_VIA_USB
#ifdef DEBUG_LOG
/* Serial echo every 5s of charger stats */
if (currentMillis - previousMillis5000ms >= INTERVAL_5_S) {
previousMillis5000ms = currentMillis;
Serial.printf("Charger AC in IAC=%fA VAC=%fV\n", charger_stat_ACcur, charger_stat_ACvol);
Serial.printf("Charger HV out IDC=%fA VDC=%fV\n", charger_stat_HVcur, charger_stat_HVvol);
Serial.printf("Charger LV out IDC=%fA VDC=%fV\n", charger_stat_LVcur, charger_stat_LVvol);
Serial.printf("Charger mode=%s\n", (charger_mode > MODE_DISABLED) ? "Enabled" : "Disabled");
Serial.printf("Charger HVset=%uV,%uA finishCurrent=%uA\n", setpoint_HV_VDC, setpoint_HV_IDC, setpoint_HV_IDC_END);
logging.printf("Charger AC in IAC=%fA VAC=%fV\n", charger_stat_ACcur, charger_stat_ACvol);
logging.printf("Charger HV out IDC=%fA VDC=%fV\n", charger_stat_HVcur, charger_stat_HVvol);
logging.printf("Charger LV out IDC=%fA VDC=%fV\n", charger_stat_LVcur, charger_stat_LVvol);
logging.printf("Charger mode=%s\n", (charger_mode > MODE_DISABLED) ? "Enabled" : "Disabled");
logging.printf("Charger HVset=%uV,%uA finishCurrent=%uA\n", setpoint_HV_VDC, setpoint_HV_IDC, setpoint_HV_IDC_END);
}
#endif
}

View file

@ -0,0 +1,328 @@
#include "comm_can.h"
#include "../../include.h"
// Parameters
CAN_device_t CAN_cfg; // CAN Config
const int rx_queue_size = 10; // Receive Queue size
volatile bool send_ok = 0;
#ifdef CAN_ADDON
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
SPIClass SPI2515;
ACAN2515 can(MCP2515_CS, SPI2515, MCP2515_INT);
static ACAN2515_Buffer16 gBuffer;
#endif //CAN_ADDON
#ifdef CANFD_ADDON
SPIClass SPI2517;
ACAN2517FD canfd(MCP2517_CS, SPI2517, MCP2517_INT);
#endif //CANFD_ADDON
// Initialization functions
void init_CAN() {
// CAN pins
#ifdef CAN_SE_PIN
pinMode(CAN_SE_PIN, OUTPUT);
digitalWrite(CAN_SE_PIN, LOW);
#endif // CAN_SE_PIN
CAN_cfg.speed = CAN_SPEED_500KBPS;
#ifdef NATIVECAN_250KBPS // Some component is requesting lower CAN speed
CAN_cfg.speed = CAN_SPEED_250KBPS;
#endif // NATIVECAN_250KBPS
CAN_cfg.tx_pin_id = CAN_TX_PIN;
CAN_cfg.rx_pin_id = CAN_RX_PIN;
CAN_cfg.rx_queue = xQueueCreate(rx_queue_size, sizeof(CAN_frame_t));
// Init CAN Module
ESP32Can.CANInit();
#ifdef CAN_ADDON
#ifdef DEBUG_LOG
logging.println("Dual CAN Bus (ESP32+MCP2515) selected");
#endif // DEBUG_LOG
gBuffer.initWithSize(25);
SPI2515.begin(MCP2515_SCK, MCP2515_MISO, MCP2515_MOSI);
ACAN2515Settings settings2515(QUARTZ_FREQUENCY, 500UL * 1000UL); // CAN bit rate 500 kb/s
settings2515.mRequestedMode = ACAN2515Settings::NormalMode;
const uint16_t errorCode2515 = can.begin(settings2515, [] { can.isr(); });
if (errorCode2515 == 0) {
#ifdef DEBUG_LOG
logging.println("Can ok");
#endif // DEBUG_LOG
} else {
#ifdef DEBUG_LOG
logging.print("Error Can: 0x");
logging.println(errorCode2515, HEX);
#endif // DEBUG_LOG
set_event(EVENT_CANMCP2515_INIT_FAILURE, (uint8_t)errorCode2515);
}
#endif // CAN_ADDON
#ifdef CANFD_ADDON
#ifdef DEBUG_LOG
logging.println("CAN FD add-on (ESP32+MCP2517) selected");
#endif // DEBUG_LOG
SPI2517.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI);
ACAN2517FDSettings settings2517(CANFD_ADDON_CRYSTAL_FREQUENCY_MHZ, 500 * 1000,
DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
settings2517.mRequestedMode = ACAN2517FDSettings::Normal20B; // ListenOnly / Normal20B / NormalFD
#else // not USE_CANFD_INTERFACE_AS_CLASSIC_CAN
settings2517.mRequestedMode = ACAN2517FDSettings::NormalFD; // ListenOnly / Normal20B / NormalFD
#endif // USE_CANFD_INTERFACE_AS_CLASSIC_CAN
const uint32_t errorCode2517 = canfd.begin(settings2517, [] { canfd.isr(); });
canfd.poll();
if (errorCode2517 == 0) {
#ifdef DEBUG_LOG
logging.print("Bit Rate prescaler: ");
logging.println(settings2517.mBitRatePrescaler);
logging.print("Arbitration Phase segment 1: ");
logging.print(settings2517.mArbitrationPhaseSegment1);
logging.print(" segment 2: ");
logging.print(settings2517.mArbitrationPhaseSegment2);
logging.print(" SJW: ");
logging.println(settings2517.mArbitrationSJW);
logging.print("Actual Arbitration Bit Rate: ");
logging.print(settings2517.actualArbitrationBitRate());
logging.print(" bit/s");
logging.print(" (Exact:");
logging.println(settings2517.exactArbitrationBitRate() ? "yes)" : "no)");
logging.print("Arbitration Sample point: ");
logging.print(settings2517.arbitrationSamplePointFromBitStart());
logging.println("%");
#endif // DEBUG_LOG
} else {
#ifdef DEBUG_LOG
logging.print("CAN-FD Configuration error 0x");
logging.println(errorCode2517, HEX);
#endif // DEBUG_LOG
set_event(EVENT_CANMCP2517FD_INIT_FAILURE, (uint8_t)errorCode2517);
}
#endif // CANFD_ADDON
}
// Transmit functions
void transmit_can(CAN_frame* tx_frame, int interface) {
if (!allowed_to_send_CAN) {
return;
}
print_can_frame(*tx_frame, frameDirection(MSG_TX));
switch (interface) {
case CAN_NATIVE:
CAN_frame_t frame;
frame.MsgID = tx_frame->ID;
frame.FIR.B.FF = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
frame.FIR.B.DLC = tx_frame->DLC;
frame.FIR.B.RTR = CAN_no_RTR;
for (uint8_t i = 0; i < tx_frame->DLC; i++) {
frame.data.u8[i] = tx_frame->data.u8[i];
}
ESP32Can.CANWriteFrame(&frame);
break;
case CAN_ADDON_MCP2515: {
#ifdef CAN_ADDON
//Struct with ACAN2515 library format, needed to use the MCP2515 library for CAN2
CANMessage MCP2515Frame;
MCP2515Frame.id = tx_frame->ID;
MCP2515Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
MCP2515Frame.len = tx_frame->DLC;
MCP2515Frame.rtr = false;
for (uint8_t i = 0; i < MCP2515Frame.len; i++) {
MCP2515Frame.data[i] = tx_frame->data.u8[i];
}
can.tryToSend(MCP2515Frame);
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
#endif //CAN_ADDON
} break;
case CANFD_NATIVE:
case CANFD_ADDON_MCP2518: {
#ifdef CANFD_ADDON
CANFDMessage MCP2518Frame;
if (tx_frame->FD) {
MCP2518Frame.type = CANFDMessage::CANFD_WITH_BIT_RATE_SWITCH;
} else { //Classic CAN message
MCP2518Frame.type = CANFDMessage::CAN_DATA;
}
MCP2518Frame.id = tx_frame->ID;
MCP2518Frame.ext = tx_frame->ext_ID ? CAN_frame_ext : CAN_frame_std;
MCP2518Frame.len = tx_frame->DLC;
for (uint8_t i = 0; i < MCP2518Frame.len; i++) {
MCP2518Frame.data[i] = tx_frame->data.u8[i];
}
send_ok = canfd.tryToSend(MCP2518Frame);
if (!send_ok) {
set_event(EVENT_CANFD_BUFFER_FULL, interface);
} else {
clear_event(EVENT_CANFD_BUFFER_FULL);
}
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
#endif //CANFD_ADDON
} break;
default:
// Invalid interface sent with function call. TODO: Raise event that coders messed up
break;
}
}
void send_can() {
if (!allowed_to_send_CAN) {
return;
}
send_can_battery();
#ifdef CAN_INVERTER_SELECTED
send_can_inverter();
#endif // CAN_INVERTER_SELECTED
#ifdef CHARGER_SELECTED
send_can_charger();
#endif // CHARGER_SELECTED
}
// Receive functions
void receive_can(CAN_frame* rx_frame, int interface) {
print_can_frame(*rx_frame, frameDirection(MSG_RX));
if (interface == can_config.battery) {
receive_can_battery(*rx_frame);
#ifdef CHADEMO_BATTERY
ISA_handleFrame(rx_frame);
#endif
}
if (interface == can_config.inverter) {
#ifdef CAN_INVERTER_SELECTED
receive_can_inverter(*rx_frame);
#endif
}
if (interface == can_config.battery_double) {
#ifdef DOUBLE_BATTERY
receive_can_battery2(*rx_frame);
#endif
}
if (interface == can_config.charger) {
#ifdef CHARGER_SELECTED
receive_can_charger(*rx_frame);
#endif
}
}
void receive_can_native() { // This section checks if we have a complete CAN message incoming on native CAN port
CAN_frame_t rx_frame_native;
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame_native, 0) == pdTRUE) {
CAN_frame rx_frame;
rx_frame.ID = rx_frame_native.MsgID;
if (rx_frame_native.FIR.B.FF == CAN_frame_std) {
rx_frame.ext_ID = false;
} else { //CAN_frame_ext == 1
rx_frame.ext_ID = true;
}
rx_frame.DLC = rx_frame_native.FIR.B.DLC;
for (uint8_t i = 0; i < rx_frame.DLC && i < 8; i++) {
rx_frame.data.u8[i] = rx_frame_native.data.u8[i];
}
//message incoming, pass it on to the handler
receive_can(&rx_frame, CAN_NATIVE);
}
}
#ifdef CAN_ADDON
void receive_can_addon() { // This section checks if we have a complete CAN message incoming on add-on CAN port
CAN_frame rx_frame; // Struct with our CAN format
CANMessage MCP2515Frame; // Struct with ACAN2515 library format, needed to use the MCP2515 library
if (can.available()) {
can.receive(MCP2515Frame);
rx_frame.ID = MCP2515Frame.id;
rx_frame.ext_ID = MCP2515Frame.ext ? CAN_frame_ext : CAN_frame_std;
rx_frame.DLC = MCP2515Frame.len;
for (uint8_t i = 0; i < MCP2515Frame.len && i < 8; i++) {
rx_frame.data.u8[i] = MCP2515Frame.data[i];
}
//message incoming, pass it on to the handler
receive_can(&rx_frame, CAN_ADDON_MCP2515);
}
}
#endif // CAN_ADDON
#ifdef CANFD_ADDON
// Functions
void receive_canfd_addon() { // This section checks if we have a complete CAN-FD message incoming
CANFDMessage frame;
int count = 0;
while (canfd.available() && count++ < 16) {
canfd.receive(frame);
CAN_frame rx_frame;
rx_frame.ID = frame.id;
rx_frame.ext_ID = frame.ext;
rx_frame.DLC = frame.len;
memcpy(rx_frame.data.u8, frame.data, MIN(rx_frame.DLC, 64));
//message incoming, pass it on to the handler
receive_can(&rx_frame, CANFD_ADDON_MCP2518);
receive_can(&rx_frame, CANFD_NATIVE);
}
}
#endif // CANFD_ADDON
// Support functions
void print_can_frame(CAN_frame frame, frameDirection msgDir) {
#ifdef DEBUG_CAN_DATA // If enabled in user settings, print out the CAN messages via USB
uint8_t i = 0;
Serial.print("(");
Serial.print(millis() / 1000.0);
(msgDir == MSG_RX) ? Serial.print(") RX0 ") : Serial.print(") TX1 ");
Serial.print(frame.ID, HEX);
Serial.print(" [");
Serial.print(frame.DLC);
Serial.print("] ");
for (i = 0; i < frame.DLC; i++) {
Serial.print(frame.data.u8[i] < 16 ? "0" : "");
Serial.print(frame.data.u8[i], HEX);
if (i < frame.DLC - 1)
Serial.print(" ");
}
Serial.println("");
#endif // DEBUG_CAN_DATA
if (datalayer.system.info.can_logging_active) { // If user clicked on CAN Logging page in webserver, start recording
char* message_string = datalayer.system.info.logged_can_messages;
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
if (offset + 128 > sizeof(datalayer.system.info.logged_can_messages)) {
// Not enough space, reset and start from the beginning
offset = 0;
}
unsigned long currentTime = millis();
// Add timestamp
offset += snprintf(message_string + offset, message_string_size - offset, "(%lu.%03lu) ", currentTime / 1000,
currentTime % 1000);
// Add direction. The 0 and 1 after RX and TX ensures that SavvyCAN puts TX and RX in a different bus.
offset +=
snprintf(message_string + offset, message_string_size - offset, "%s ", (msgDir == MSG_RX) ? "RX0" : "TX1");
// Add ID and DLC
offset += snprintf(message_string + offset, message_string_size - offset, "%X [%u] ", frame.ID, frame.DLC);
// Add data bytes
for (uint8_t i = 0; i < frame.DLC; i++) {
if (i < frame.DLC - 1) {
offset += snprintf(message_string + offset, message_string_size - offset, "%02X ", frame.data.u8[i]);
} else {
offset += snprintf(message_string + offset, message_string_size - offset, "%02X", frame.data.u8[i]);
}
}
// Add linebreak
offset += snprintf(message_string + offset, message_string_size - offset, "\n");
datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer
}
}

View file

@ -0,0 +1,93 @@
#ifndef _COMM_CAN_H_
#define _COMM_CAN_H_
#include "../../include.h"
#include "../../datalayer/datalayer.h"
#include "../../devboard/utils/events.h"
#include "../../devboard/utils/value_mapping.h"
#include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#ifdef CAN_ADDON
#include "../../lib/pierremolinaro-acan2515/ACAN2515.h"
#endif //CAN_ADDON
#ifdef CANFD_ADDON
#include "../../lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
#endif //CANFD_ADDON
enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1
/**
* @brief Initialization function for CAN.
*
* @param[in] void
*
* @return void
*/
void init_CAN();
/**
* @brief Transmit one CAN frame
*
* @param[in] CAN_frame* tx_frame
* @param[in] int interface
*
* @return void
*/
void transmit_can();
/**
* @brief Send CAN messages to all components
*
* @param[in] void
*
* @return void
*/
void send_can();
/**
* @brief Receive CAN messages from all interfaces
*
* @param[in] void
*
* @return void
*/
void receive_can();
/**
* @brief Receive CAN messages from CAN tranceiver natively installed on Lilygo hardware
*
* @param[in] void
*
* @return void
*/
void receive_can_native();
/**
* @brief Receive CAN messages from CAN addon chip
*
* @param[in] void
*
* @return void
*/
void receive_can_addon();
/**
* @brief Receive CAN messages from CANFD addon chip
*
* @param[in] void
*
* @return void
*/
void receive_canfd_addon();
/**
* @brief print CAN frames via USB
*
* @param[in] void
*
* @return void
*/
void print_can_frame(CAN_frame frame, frameDirection msgDir);
#endif

View file

@ -0,0 +1,204 @@
#include "comm_contactorcontrol.h"
#include "../../include.h"
// Parameters
#ifndef CONTACTOR_CONTROL
#ifdef PWM_CONTACTOR_CONTROL
#error CONTACTOR_CONTROL needs to be enabled for PWM_CONTACTOR_CONTROL
#endif
#endif
#ifdef CONTACTOR_CONTROL
enum State { DISCONNECTED, START_PRECHARGE, PRECHARGE, POSITIVE, PRECHARGE_OFF, COMPLETED, SHUTDOWN_REQUESTED };
State contactorStatus = DISCONNECTED;
#define ON 1
#define OFF 0
#ifdef NC_CONTACTORS //Normally closed contactors use inverted logic
#undef ON
#define ON 0
#undef OFF
#define OFF 1
#endif //NC_CONTACTORS
#define MAX_ALLOWED_FAULT_TICKS 1000
#define NEGATIVE_CONTACTOR_TIME_MS \
500 // Time after negative contactor is turned on, to start precharge (not actual precharge time!)
#define PRECHARGE_COMPLETED_TIME_MS \
1000 // After successful precharge, resistor is turned off after this delay (and contactors are economized if PWM enabled)
#define PWM_Freq 20000 // 20 kHz frequency, beyond audible range
#define PWM_Res 10 // 10 Bit resolution 0 to 1023, maps 'nicely' to 0% 100%
#define PWM_HOLD_DUTY 250
#define PWM_OFF_DUTY 0
#define PWM_ON_DUTY 1023
#define PWM_Positive_Channel 0
#define PWM_Negative_Channel 1
unsigned long prechargeStartTime = 0;
unsigned long negativeStartTime = 0;
unsigned long prechargeCompletedTime = 0;
unsigned long timeSpentInFaultedMode = 0;
#endif
void set(uint8_t pin, bool direction, uint32_t pwm_freq = 0xFFFF) {
#ifdef PWM_CONTACTOR_CONTROL
if (pwm_freq != 0xFFFF) {
ledcWrite(pin, pwm_freq);
return;
}
#endif
if (direction == 1) {
digitalWrite(pin, HIGH);
} else { // 0
digitalWrite(pin, LOW);
}
}
// Initialization functions
void init_contactors() {
// Init contactor pins
#ifdef CONTACTOR_CONTROL
#ifdef PWM_CONTACTOR_CONTROL
// Setup PWM Channel Frequency and Resolution
ledcAttachChannel(POSITIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Positive_Channel);
ledcAttachChannel(NEGATIVE_CONTACTOR_PIN, PWM_Freq, PWM_Res, PWM_Negative_Channel);
// Set all pins OFF (0% PWM)
ledcWrite(POSITIVE_CONTACTOR_PIN, PWM_OFF_DUTY);
ledcWrite(NEGATIVE_CONTACTOR_PIN, PWM_OFF_DUTY);
#else //Normal CONTACTOR_CONTROL
pinMode(POSITIVE_CONTACTOR_PIN, OUTPUT);
set(POSITIVE_CONTACTOR_PIN, OFF);
pinMode(NEGATIVE_CONTACTOR_PIN, OUTPUT);
set(NEGATIVE_CONTACTOR_PIN, OFF);
#endif // Precharge never has PWM regardless of setting
pinMode(PRECHARGE_PIN, OUTPUT);
set(PRECHARGE_PIN, OFF);
#endif // CONTACTOR_CONTROL
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
pinMode(SECOND_POSITIVE_CONTACTOR_PIN, OUTPUT);
set(SECOND_POSITIVE_CONTACTOR_PIN, OFF);
pinMode(SECOND_NEGATIVE_CONTACTOR_PIN, OUTPUT);
set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF);
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
// Init BMS contactor
#ifdef HW_STARK // TODO: Rewrite this so LilyGo can also handle this BMS contactor
pinMode(BMS_POWER, OUTPUT);
digitalWrite(BMS_POWER, HIGH);
#endif // HW_STARK
}
// Main functions
void handle_contactors() {
#if defined(BYD_SMA) || defined(SMA_TRIPOWER_CAN)
datalayer.system.status.inverter_allows_contactor_closing = digitalRead(INVERTER_CONTACTOR_ENABLE_PIN);
#endif
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
handle_contactors_battery2();
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
#ifdef CONTACTOR_CONTROL
// First check if we have any active errors, incase we do, turn off the battery
if (datalayer.battery.status.bms_status == FAULT) {
timeSpentInFaultedMode++;
} else {
timeSpentInFaultedMode = 0;
}
//handle contactor control SHUTDOWN_REQUESTED
if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS) {
contactorStatus = SHUTDOWN_REQUESTED;
}
if (contactorStatus == SHUTDOWN_REQUESTED) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set_event(EVENT_ERROR_OPEN_CONTACTOR, 0);
datalayer.system.status.contactors_engaged = false;
return; // A fault scenario latches the contactor control. It is not possible to recover without a powercycle (and investigation why fault occured)
}
// After that, check if we are OK to start turning on the battery
if (contactorStatus == DISCONNECTED) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
set(POSITIVE_CONTACTOR_PIN, OFF, PWM_OFF_DUTY);
if (datalayer.system.status.battery_allows_contactor_closing &&
datalayer.system.status.inverter_allows_contactor_closing && !datalayer.system.settings.equipment_stop_active) {
contactorStatus = START_PRECHARGE;
}
}
// In case the inverter requests contactors to open, set the state accordingly
if (contactorStatus == COMPLETED) {
//Incase inverter (or estop) requests contactors to open, make state machine jump to Disconnected state (recoverable)
if (!datalayer.system.status.inverter_allows_contactor_closing || datalayer.system.settings.equipment_stop_active) {
contactorStatus = DISCONNECTED;
}
// Skip running the state machine below if it has already completed
return;
}
unsigned long currentTime = millis();
if (currentTime < INTERVAL_10_S) {
// Skip running the state machine before system has started up.
// Gives the system some time to detect any faults from battery before blindly just engaging the contactors
return;
}
// Handle actual state machine. This first turns on Negative, then Precharge, then Positive, and finally turns OFF precharge
switch (contactorStatus) {
case START_PRECHARGE:
set(NEGATIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY);
prechargeStartTime = currentTime;
contactorStatus = PRECHARGE;
break;
case PRECHARGE:
if (currentTime - prechargeStartTime >= NEGATIVE_CONTACTOR_TIME_MS) {
set(PRECHARGE_PIN, ON);
negativeStartTime = currentTime;
contactorStatus = POSITIVE;
}
break;
case POSITIVE:
if (currentTime - negativeStartTime >= PRECHARGE_TIME_MS) {
set(POSITIVE_CONTACTOR_PIN, ON, PWM_ON_DUTY);
prechargeCompletedTime = currentTime;
contactorStatus = PRECHARGE_OFF;
}
break;
case PRECHARGE_OFF:
if (currentTime - prechargeCompletedTime >= PRECHARGE_COMPLETED_TIME_MS) {
set(PRECHARGE_PIN, OFF);
set(NEGATIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY);
set(POSITIVE_CONTACTOR_PIN, ON, PWM_HOLD_DUTY);
contactorStatus = COMPLETED;
datalayer.system.status.contactors_engaged = true;
}
break;
default:
break;
}
#endif // CONTACTOR_CONTROL
}
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
void handle_contactors_battery2() {
if ((contactorStatus == COMPLETED) && datalayer.system.status.battery2_allows_contactor_closing) {
set(SECOND_NEGATIVE_CONTACTOR_PIN, ON);
set(SECOND_POSITIVE_CONTACTOR_PIN, ON);
datalayer.system.status.contactors_battery2_engaged = true;
} else { // Closing contactors on secondary battery not allowed
set(SECOND_NEGATIVE_CONTACTOR_PIN, OFF);
set(SECOND_POSITIVE_CONTACTOR_PIN, OFF);
datalayer.system.status.contactors_battery2_engaged = false;
}
}
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY

View file

@ -0,0 +1,36 @@
#ifndef _COMM_CONTACTORCONTROL_H_
#define _COMM_CONTACTORCONTROL_H_
#include "../../include.h"
#include "../../datalayer/datalayer.h"
#include "../../devboard/utils/events.h"
/**
* @brief Contactor initialization
*
* @param[in] void
*
* @return void
*/
void init_contactors();
/**
* @brief Handle contactors
*
* @param[in] void
*
* @return void
*/
void handle_contactors();
/**
* @brief Handle contactors of battery 2
*
* @param[in] void
*
* @return void
*/
void handle_contactors_battery2();
#endif

View file

@ -0,0 +1,51 @@
#include "comm_equipmentstopbutton.h"
#include "../../include.h"
// Parameters
#ifdef EQUIPMENT_STOP_BUTTON
const unsigned long equipment_button_long_press_duration =
15000; // 15 seconds for long press in case of MOMENTARY_SWITCH
const unsigned long equipment_button_debounce_duration = 200; // 200ms for debouncing the button
unsigned long timeSincePress = 0; // Variable to store the time since the last press
DebouncedButton equipment_stop_button; // Debounced button object
#endif // EQUIPMENT_STOP_BUTTON
// Initialization functions
#ifdef EQUIPMENT_STOP_BUTTON
void init_equipment_stop_button() {
//using external pullup resistors NC
pinMode(EQUIPMENT_STOP_PIN, INPUT);
// Initialize the debounced button with NC switch type and equipment_button_debounce_duration debounce time
initDebouncedButton(equipment_stop_button, EQUIPMENT_STOP_PIN, NC, equipment_button_debounce_duration);
}
#endif // EQUIPMENT_STOP_BUTTON
// Main functions
#ifdef EQUIPMENT_STOP_BUTTON
void monitor_equipment_stop_button() {
ButtonState changed_state = debounceButton(equipment_stop_button, timeSincePress);
if (equipment_stop_behavior == LATCHING_SWITCH) {
if (changed_state == PRESSED) {
// Changed to ON initiating equipment stop.
setBatteryPause(true, false, true);
} else if (changed_state == RELEASED) {
// Changed to OFF ending equipment stop.
setBatteryPause(false, false, false);
}
} else if (equipment_stop_behavior == MOMENTARY_SWITCH) {
if (changed_state == RELEASED) { // button is released
if (timeSincePress < equipment_button_long_press_duration) {
// Short press detected, trigger equipment stop
setBatteryPause(true, false, true);
} else {
// Long press detected, reset equipment stop state
setBatteryPause(false, false, false);
}
}
}
}
#endif // EQUIPMENT_STOP_BUTTON

View file

@ -0,0 +1,28 @@
#ifndef _COMM_EQUIPMENTSTOPBUTTON_H_
#define _COMM_EQUIPMENTSTOPBUTTON_H_
#include "../../include.h"
#ifdef EQUIPMENT_STOP_BUTTON
#include "../../devboard/utils/debounce_button.h"
#endif
/**
* @brief Initialization of equipment stop button
*
* @param[in] void
*
* @return void
*/
void init_equipment_stop_button();
/**
* @brief Monitor equipment stop button
*
* @param[in] void
*
* @return void
*/
void monitor_equipment_stop_button();
#endif

View file

@ -0,0 +1,125 @@
#include "comm_nvm.h"
#include "../../include.h"
// Parameters
Preferences settings; // Store user settings
// Initialization functions
void init_stored_settings() {
static uint32_t temp = 0;
// ATTENTION ! The maximum length for settings keys is 15 characters
settings.begin("batterySettings", false);
// Always get the equipment stop status
datalayer.system.settings.equipment_stop_active = settings.getBool("EQUIPMENT_STOP", false);
if (datalayer.system.settings.equipment_stop_active) {
set_event(EVENT_EQUIPMENT_STOP, 1);
}
#ifndef LOAD_SAVED_SETTINGS_ON_BOOT
settings.clear(); // If this clear function is executed, no settings will be read from storage
//always save the equipment stop status
settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active);
#endif // LOAD_SAVED_SETTINGS_ON_BOOT
#ifdef WIFI
char tempSSIDstring[63]; // Allocate buffer with sufficient size
size_t lengthSSID = settings.getString("SSID", tempSSIDstring, sizeof(tempSSIDstring));
if (lengthSSID > 0) { // Successfully read the string from memory. Set it to SSID!
ssid = tempSSIDstring;
} else { // Reading from settings failed. Do nothing with SSID. Raise event?
}
char tempPasswordString[63]; // Allocate buffer with sufficient size
size_t lengthPassword = settings.getString("PASSWORD", tempPasswordString, sizeof(tempPasswordString));
if (lengthPassword > 7) { // Successfully read the string from memory. Set it to password!
password = tempPasswordString;
} else { // Reading from settings failed. Do nothing with SSID. Raise event?
}
#endif // WIFI
temp = settings.getUInt("BATTERY_WH_MAX", false);
if (temp != 0) {
datalayer.battery.info.total_capacity_Wh = temp;
}
temp = settings.getUInt("MAXPERCENTAGE", false);
if (temp != 0) {
datalayer.battery.settings.max_percentage = temp * 10; // Multiply by 10 for backwards compatibility
}
temp = settings.getUInt("MINPERCENTAGE", false);
if (temp != 0) {
datalayer.battery.settings.min_percentage = temp * 10; // Multiply by 10 for backwards compatibility
}
temp = settings.getUInt("MAXCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_charge_dA = temp;
}
temp = settings.getUInt("MAXDISCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_discharge_dA = temp;
}
datalayer.battery.settings.soc_scaling_active = settings.getBool("USE_SCALED_SOC", false);
temp = settings.getUInt("TARGETCHVOLT", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_charge_voltage_dV = temp;
}
temp = settings.getUInt("TARGETDISCHVOLT", false);
if (temp != 0) {
datalayer.battery.settings.max_user_set_discharge_voltage_dV = temp;
}
datalayer.battery.settings.user_set_voltage_limits_active = settings.getBool("USEVOLTLIMITS", false);
settings.end();
}
void store_settings_equipment_stop() {
settings.begin("batterySettings", false);
settings.putBool("EQUIPMENT_STOP", datalayer.system.settings.equipment_stop_active);
settings.end();
}
void store_settings() {
// ATTENTION ! The maximum length for settings keys is 15 characters
if (!settings.begin("batterySettings", false)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 0);
return;
}
#ifdef WIFI
if (!settings.putString("SSID", String(ssid.c_str()))) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 1);
}
if (!settings.putString("PASSWORD", String(password.c_str()))) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 2);
}
#endif
if (!settings.putUInt("BATTERY_WH_MAX", datalayer.battery.info.total_capacity_Wh)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 3);
}
if (!settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 4);
}
if (!settings.putUInt("MAXPERCENTAGE", datalayer.battery.settings.max_percentage / 10)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 5);
}
if (!settings.putUInt("MINPERCENTAGE", datalayer.battery.settings.min_percentage / 10)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 6);
}
if (!settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 7);
}
if (!settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 8);
}
if (!settings.putBool("USEVOLTLIMITS", datalayer.battery.settings.user_set_voltage_limits_active)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 9);
}
if (!settings.putUInt("TARGETCHVOLT", datalayer.battery.settings.max_user_set_charge_voltage_dV)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 10);
}
if (!settings.putUInt("TARGETDISCHVOLT", datalayer.battery.settings.max_user_set_discharge_voltage_dV)) {
set_event(EVENT_PERSISTENT_SAVE_INFO, 11);
}
settings.end(); // Close preferences handle
}

View file

@ -0,0 +1,37 @@
#ifndef _COMM_NVM_H_
#define _COMM_NVM_H_
#include "../../include.h"
#include "../../datalayer/datalayer.h"
#include "../../devboard/utils/events.h"
#include "../../devboard/wifi/wifi.h"
/**
* @brief Initialization of setting storage
*
* @param[in] void
*
* @return void
*/
void init_stored_settings();
/**
* @brief Store settings of equipment stop button
*
* @param[in] void
*
* @return void
*/
void store_settings_equipment_stop();
/**
* @brief Store settings
*
* @param[in] void
*
* @return void
*/
void store_settings();
#endif

View file

@ -0,0 +1,51 @@
#include "comm_rs485.h"
#include "../../include.h"
// Parameters
#ifdef MODBUS_INVERTER_SELECTED
#define MB_RTU_NUM_VALUES 13100
uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
ModbusServerRTU MBserver(Serial2, 2000);
#endif
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
#define SERIAL_LINK_BAUDRATE 112500
#endif
// Initialization functions
void init_rs485() {
// Set up Modbus RTU Server
#ifdef RS485_EN_PIN
pinMode(RS485_EN_PIN, OUTPUT);
digitalWrite(RS485_EN_PIN, HIGH);
#endif // RS485_EN_PIN
#ifdef RS485_SE_PIN
pinMode(RS485_SE_PIN, OUTPUT);
digitalWrite(RS485_SE_PIN, HIGH);
#endif // RS485_SE_PIN
#ifdef PIN_5V_EN
pinMode(PIN_5V_EN, OUTPUT);
digitalWrite(PIN_5V_EN, HIGH);
#endif // PIN_5V_EN
#ifdef RS485_INVERTER_SELECTED
Serial2.begin(57600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif // RS485_INVERTER_SELECTED
#ifdef MODBUS_INVERTER_SELECTED
#ifdef BYD_MODBUS
// Init Static data to the RTU Modbus
handle_static_data_modbus_byd();
#endif // BYD_MODBUS
// Init Serial2 connected to the RTU Modbus
RTUutils::prepareHardwareSerial(Serial2);
Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
// Register served function code worker for server
MBserver.registerWorker(MBTCP_ID, READ_HOLD_REGISTER, &FC03);
MBserver.registerWorker(MBTCP_ID, WRITE_HOLD_REGISTER, &FC06);
MBserver.registerWorker(MBTCP_ID, WRITE_MULT_REGISTERS, &FC16);
MBserver.registerWorker(MBTCP_ID, R_W_MULT_REGISTERS, &FC23);
// Start ModbusRTU background task
MBserver.begin(Serial2, MODBUS_CORE);
#endif // MODBUS_INVERTER_SELECTED
}

View file

@ -0,0 +1,19 @@
#ifndef _COMM_RS485_H_
#define _COMM_RS485_H_
#include "../../include.h"
#include "../../lib/eModbus-eModbus/Logging.h"
#include "../../lib/eModbus-eModbus/ModbusServerRTU.h"
#include "../../lib/eModbus-eModbus/scripts/mbServerFCs.h"
/**
* @brief Initialization of RS485
*
* @param[in] void
*
* @return void
*/
void init_rs485();
#endif

View file

@ -0,0 +1,35 @@
#include "comm_seriallink.h"
#include "../../include.h"
// Parameters
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
#define SERIAL_LINK_BAUDRATE 112500
#endif
// Initialization functions
void init_serialDataLink() {
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
Serial2.begin(SERIAL_LINK_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
#endif // SERIAL_LINK_RECEIVER || SERIAL_LINK_TRANSMITTER
}
// Main functions
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
void run_serialDataLink() {
static unsigned long updateTime = 0;
unsigned long currentMillis = millis();
if ((currentMillis - updateTime) > 1) { //Every 2ms
updateTime = currentMillis;
#ifdef SERIAL_LINK_RECEIVER
manageSerialLinkReceiver();
#endif
#ifdef SERIAL_LINK_TRANSMITTER
manageSerialLinkTransmitter();
#endif
}
}
#endif // SERIAL_LINK_RECEIVER || SERIAL_LINK_TRANSMITTER

View file

@ -0,0 +1,17 @@
#ifndef _COMM_SERIALLINK_H_
#define _COMM_SERIALLINK_H_
#include "../../include.h"
/**
* @brief Initialization of serial data link
*
* @param[in] void
*
* @return void
*/
void init_serialDataLink();
void run_serialDataLink();
#endif

View file

@ -13,10 +13,12 @@ typedef struct {
uint16_t max_design_voltage_dV = 5000;
/** The minimum intended packvoltage, in deciVolt. 3300 = 330.0 V */
uint16_t min_design_voltage_dV = 2500;
/** BYD CAN specific setting, max charge in deciAmpere. 300 = 30.0 A */
uint16_t max_charge_amp_dA = BATTERY_MAX_CHARGE_AMP;
/** BYD CAN specific setting, max discharge in deciAmpere. 300 = 30.0 A */
uint16_t max_discharge_amp_dA = BATTERY_MAX_DISCHARGE_AMP;
/** The maximum cellvoltage before shutting down, in milliVolt. 4300 = 4.250 V */
uint16_t max_cell_voltage_mV = 4300;
/** The minimum cellvoltage before shutting down, in milliVolt. 2700 = 2.700 V */
uint16_t min_cell_voltage_mV = 2700;
/** The maxumum allowed deviation between cells, in milliVolt. 500 = 0.500 V */
uint16_t max_cell_voltage_deviation_mV = 500;
/** uint8_t */
/** Total number of cells in the pack */
@ -29,7 +31,7 @@ typedef struct {
typedef struct {
/** int32_t */
/** Instantaneous battery power in Watts */
/** Instantaneous battery power in Watts. Calculated based on voltage_dV and current_dA */
/* Positive value = Battery Charging */
/* Negative value = Battery Discharging */
int32_t active_power_W;
@ -37,10 +39,20 @@ typedef struct {
/** uint32_t */
/** Remaining energy capacity in Watt-hours */
uint32_t remaining_capacity_Wh;
/** Maximum allowed battery discharge power in Watts */
/** The remaining capacity reported to the inverter based on min percentage setting, in Watt-hours
* This value will either be scaled or not scaled depending on the value of
* battery.settings.soc_scaling_active
*/
uint32_t reported_remaining_capacity_Wh;
/** Maximum allowed battery discharge power in Watts. Set by battery */
uint32_t max_discharge_power_W = 0;
/** Maximum allowed battery charge power in Watts */
/** Maximum allowed battery charge power in Watts. Set by battery */
uint32_t max_charge_power_W = 0;
/** Maximum allowed battery discharge current in dA. Calculated based on allowed W and Voltage */
uint16_t max_discharge_current_dA = 0;
/** Maximum allowed battery charge current in dA. Calculated based on allowed W and Voltage */
uint16_t max_charge_current_dA = 0;
/** int16_t */
/** Maximum temperature currently measured in the pack, in d°C. 150 = 15.0 °C */
@ -95,6 +107,20 @@ typedef struct {
* you want the inverter to be able to use. At this real SOC, the inverter
* will "see" 100% */
uint16_t max_percentage = BATTERY_MAXPERCENTAGE;
/** The user specified maximum allowed charge rate, in deciAmpere. 300 = 30.0 A */
uint16_t max_user_set_charge_dA = BATTERY_MAX_CHARGE_AMP;
/** The user specified maximum allowed discharge rate, in deciAmpere. 300 = 30.0 A */
uint16_t max_user_set_discharge_dA = BATTERY_MAX_DISCHARGE_AMP;
/** User specified discharge/charge voltages in use. Set to true to use user specified values */
/** Some inverters like to see a specific target voltage for charge/discharge. Use these values to override automatic voltage limits*/
bool user_set_voltage_limits_active = BATTERY_USE_VOLTAGE_LIMITS;
/** The user specified maximum allowed charge voltage, in deciVolt. 4000 = 400.0 V */
uint16_t max_user_set_charge_voltage_dV = BATTERY_MAX_CHARGE_VOLTAGE;
/** The user specified maximum allowed discharge voltage, in deciVolt. 3000 = 300.0 V */
uint16_t max_user_set_discharge_voltage_dV = BATTERY_MAX_DISCHARGE_VOLTAGE;
} DATALAYER_BATTERY_SETTINGS_TYPE;
typedef struct {
@ -111,7 +137,16 @@ typedef struct {
} DATALAYER_SHUNT_TYPE;
typedef struct {
// TODO
/** array with type of battery used, for displaying on webserver */
char battery_protocol[64] = {0};
/** array with type of inverter used, for displaying on webserver */
char inverter_protocol[64] = {0};
/** array with incoming CAN messages, for displaying on webserver */
char logged_can_messages[15000] = {0};
size_t logged_can_messages_offset = 0;
/** bool, determines if CAN messages should be logged for webserver */
bool can_logging_active = false;
} DATALAYER_SYSTEM_INFO_TYPE;
typedef struct {
@ -133,8 +168,8 @@ typedef struct {
int64_t time_comm_us = 0;
/** 10 ms function measurement variable */
int64_t time_10ms_us = 0;
/** 5 s function measurement variable */
int64_t time_5s_us = 0;
/** Value update function measurement variable */
int64_t time_values_us = 0;
/** CAN TX function measurement variable */
int64_t time_cantx_us = 0;
@ -151,23 +186,36 @@ typedef struct {
*/
int64_t time_snap_10ms_us = 0;
/** Function measurement snapshot variable.
* This will show the performance of the 5 s functionality of the core task when the total time reached a new worst case
* This will show the performance of the values functionality of the core task when the total time reached a new worst case
*/
int64_t time_snap_5s_us = 0;
int64_t time_snap_values_us = 0;
/** Function measurement snapshot variable.
* This will show the performance of CAN TX when the total time reached a new worst case
*/
int64_t time_snap_cantx_us = 0;
#endif
/** uint8_t */
/** A counter set each time a new message comes from inverter.
* This value then gets decremented each 5 seconds. Incase we reach 0
* we report the inverter as missing entirely on the CAN bus.
*/
uint8_t CAN_inverter_still_alive = CAN_STILL_ALIVE;
/** True if the battery allows for the contactors to close */
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 */
bool inverter_allows_contactor_closing = true;
#ifdef CONTACTOR_CONTROL
/** True if the contactor controlled by battery-emulator is closed */
bool contactors_engaged = false;
/** True if the contactor controlled by battery-emulator is closed. Determined by check_interconnect_available(); if voltage is OK */
bool contactors_battery2_engaged = false;
#endif
} DATALAYER_SYSTEM_STATUS_TYPE;
typedef struct {
bool equipment_stop_active = false;
} DATALAYER_SYSTEM_SETTINGS_TYPE;
typedef struct {

View file

@ -0,0 +1,4 @@
#include "datalayer_extended.h"
#include "../include.h"
DataLayerExtended datalayer_extended;

View file

@ -0,0 +1,587 @@
#ifndef _DATALAYER_EXTENDED_H_
#define _DATALAYER_EXTENDED_H_
#include "../include.h"
typedef struct {
/** uint16_t */
/** PID polling parameters */
uint16_t battery_5V_ref = 0;
int16_t battery_module_temp_1 = 0;
int16_t battery_module_temp_2 = 0;
int16_t battery_module_temp_3 = 0;
int16_t battery_module_temp_4 = 0;
int16_t battery_module_temp_5 = 0;
int16_t battery_module_temp_6 = 0;
uint16_t battery_cell_average_voltage = 0;
uint16_t battery_cell_average_voltage_2 = 0;
uint16_t battery_terminal_voltage = 0;
uint16_t battery_ignition_power_mode = 0;
int16_t battery_current_7E7 = 0;
uint16_t battery_capacity_my17_18 = 0;
uint16_t battery_capacity_my19plus = 0;
uint16_t battery_SOC_display = 0;
uint16_t battery_SOC_raw_highprec = 0;
uint16_t battery_max_temperature = 0;
uint16_t battery_min_temperature = 0;
uint16_t battery_max_cell_voltage = 0;
uint16_t battery_min_cell_voltage = 0;
uint16_t battery_lowest_cell = 0;
uint16_t battery_highest_cell = 0;
uint16_t battery_internal_resistance = 0;
uint16_t battery_voltage_polled = 0;
uint16_t battery_vehicle_isolation = 0;
uint16_t battery_isolation_kohm = 0;
uint16_t battery_HV_locked = 0;
uint16_t battery_crash_event = 0;
uint16_t battery_HVIL = 0;
uint16_t battery_HVIL_status = 0;
int16_t battery_current_7E4 = 0;
} DATALAYER_INFO_BOLTAMPERA;
typedef struct {
/** uint16_t */
/** Terminal 30 - 12V SME Supply Voltage */
uint16_t T30_Voltage = 0;
/** Status HVIL, 1 HVIL OK, 0 HVIL disconnected*/
uint8_t hvil_status = 0;
/** Min/Max Cell SOH*/
uint16_t min_soh_state = 0;
uint16_t max_soh_state = 0;
uint32_t bms_uptime = 0;
uint8_t pyro_status_pss1 = 0;
uint8_t pyro_status_pss4 = 0;
uint8_t pyro_status_pss6 = 0;
int32_t iso_safety_positive = 0;
int32_t iso_safety_negative = 0;
int32_t iso_safety_parallel = 0;
int32_t allowable_charge_amps = 0;
int32_t allowable_discharge_amps = 0;
int16_t balancing_status = 0;
int16_t battery_voltage_after_contactor = 0;
unsigned long min_cell_voltage_data_age = 0;
unsigned long max_cell_voltage_data_age = 0;
} DATALAYER_INFO_BMWIX;
typedef struct {
/** uint16_t */
/** SOC% raw battery value. Might not always reach 100% */
uint16_t SOC_raw = 0;
/** uint16_t */
/** SOC% instrumentation cluster value. Will always reach 100% */
uint16_t SOC_dash = 0;
/** uint16_t */
/** SOC% OBD2 value, polled actively */
uint16_t SOC_OBD2 = 0;
/** uint8_t */
/** Status isolation external, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/
uint8_t ST_iso_ext = 0;
/** uint8_t */
/** Status isolation external, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/
uint8_t ST_iso_int = 0;
/** uint8_t */
/** Status cooling valve error, 0 not evaluated, 1 OK valve closed, 2 error active valve open, 3 Invalid signal*/
uint8_t ST_valve_cooling = 0;
/** uint8_t */
/** Status interlock error, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/
uint8_t ST_interlock = 0;
/** uint8_t */
/** Status precharge, 0 no statement, 1 Not active closing not blocked, 2 error precharge blocked, 3 Invalid signal*/
uint8_t ST_precharge = 0;
/** uint8_t */
/** Status DC switch, 0 contactors open, 1 precharge ongoing, 2 contactors engaged, 3 Invalid signal*/
uint8_t ST_DCSW = 0;
/** uint8_t */
/** Status emergency, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/
uint8_t ST_EMG = 0;
/** uint8_t */
/** Status welding detection, 0 Contactors OK, 1 One contactor welded, 2 Two contactors welded, 3 Invalid signal*/
uint8_t ST_WELD = 0;
/** uint8_t */
/** Status isolation, 0 not evaluated, 1 OK, 2 error active, 3 Invalid signal*/
uint8_t ST_isolation = 0;
/** uint8_t */
/** Status cold shutoff valve, 0 OK, 1 Short circuit to GND, 2 Short circuit to 12V, 3 Line break, 6 Driver error, 12 Stuck, 13 Stuck, 15 Invalid Signal*/
uint8_t ST_cold_shutoff_valve = 0;
} DATALAYER_INFO_BMWI3;
typedef struct {
/** bool */
/** Which SOC method currently used. 0 = Estimated, 1 = Measured */
bool SOC_method = 0;
/** uint16_t */
/** SOC% estimate. Estimated from total pack voltage */
uint16_t SOC_estimated = 0;
/** uint16_t */
/** SOC% raw battery value. Highprecision. Can be locked if pack is crashed */
uint16_t SOC_highprec = 0;
/** uint16_t */
/** SOC% polled OBD2 value. Can be locked if pack is crashed */
uint16_t SOC_polled = 0;
/** uint16_t */
/** Voltage raw battery value */
uint16_t voltage_periodic = 0;
/** uint16_t */
/** Voltage polled OBD2*/
uint16_t voltage_polled = 0;
} DATALAYER_INFO_BYDATTO3;
typedef struct {
/** bool */
/** All values either True or false */
bool system_state_discharge = false;
bool system_state_charge = false;
bool system_state_cellbalancing = false;
bool system_state_tricklecharge = false;
bool system_state_idle = false;
bool system_state_chargecompleted = false;
bool system_state_maintenancecharge = false;
bool IO_state_main_positive_relay = false;
bool IO_state_main_negative_relay = false;
bool IO_state_charge_enable = false;
bool IO_state_precharge_relay = false;
bool IO_state_discharge_enable = false;
bool IO_state_IO_6 = false;
bool IO_state_IO_7 = false;
bool IO_state_IO_8 = false;
bool error_Cell_overvoltage = false;
bool error_Cell_undervoltage = false;
bool error_Cell_end_of_life_voltage = false;
bool error_Cell_voltage_misread = false;
bool error_Cell_over_temperature = false;
bool error_Cell_under_temperature = false;
bool error_Cell_unmanaged = false;
bool error_LMU_over_temperature = false;
bool error_LMU_under_temperature = false;
bool error_Temp_sensor_open_circuit = false;
bool error_Temp_sensor_short_circuit = false;
bool error_SUB_communication = false;
bool error_LMU_communication = false;
bool error_Over_current_IN = false;
bool error_Over_current_OUT = false;
bool error_Short_circuit = false;
bool error_Leak_detected = false;
bool error_Leak_detection_failed = false;
bool error_Voltage_difference = false;
bool error_BMCU_supply_over_voltage = false;
bool error_BMCU_supply_under_voltage = false;
bool error_Main_positive_contactor = false;
bool error_Main_negative_contactor = false;
bool error_Precharge_contactor = false;
bool error_Midpack_contactor = false;
bool error_Precharge_timeout = false;
bool error_Emergency_connector_override = false;
bool warning_High_cell_voltage = false;
bool warning_Low_cell_voltage = false;
bool warning_High_cell_temperature = false;
bool warning_Low_cell_temperature = false;
bool warning_High_LMU_temperature = false;
bool warning_Low_LMU_temperature = false;
bool warning_SUB_communication_interfered = false;
bool warning_LMU_communication_interfered = false;
bool warning_High_current_IN = false;
bool warning_High_current_OUT = false;
bool warning_Pack_resistance_difference = false;
bool warning_High_pack_resistance = false;
bool warning_Cell_resistance_difference = false;
bool warning_High_cell_resistance = false;
bool warning_High_BMCU_supply_voltage = false;
bool warning_Low_BMCU_supply_voltage = false;
bool warning_Low_SOC = false;
bool warning_Balancing_required_OCV_model = false;
bool warning_Charger_not_responding = false;
} DATALAYER_INFO_CELLPOWER;
typedef struct {
/** uint8_t */
/** Contactor status */
uint8_t status_contactor = 0;
/** uint8_t */
/** Contactor status */
uint8_t hvil_status = 0;
/** uint8_t */
/** Negative contactor state */
uint8_t packContNegativeState = 0;
/** uint8_t */
/** Positive contactor state */
uint8_t packContPositiveState = 0;
/** uint8_t */
/** Set state of contactors */
uint8_t packContactorSetState = 0;
/** uint8_t */
/** Battery pack allows closing of contacors */
uint8_t packCtrsClosingAllowed = 0;
/** uint8_t */
/** Pyro test in progress */
bool pyroTestInProgress = false;
bool battery_packCtrsOpenNowRequested = false;
bool battery_packCtrsOpenRequested = false;
uint8_t battery_packCtrsRequestStatus = 0;
bool battery_packCtrsResetRequestRequired = false;
bool battery_dcLinkAllowedToEnergize = false;
uint8_t battery_beginning_of_life = 0;
uint8_t battery_battTempPct = 0;
uint16_t battery_dcdcLvBusVolt = 0;
uint16_t battery_dcdcHvBusVolt = 0;
uint16_t battery_dcdcLvOutputCurrent = 0;
uint16_t battery_nominal_full_pack_energy = 0;
uint16_t battery_nominal_full_pack_energy_m0 = 0;
uint16_t battery_nominal_energy_remaining = 0;
uint16_t battery_nominal_energy_remaining_m0 = 0;
uint16_t battery_ideal_energy_remaining = 0;
uint16_t battery_ideal_energy_remaining_m0 = 0;
uint16_t battery_energy_to_charge_complete = 0;
uint16_t battery_energy_to_charge_complete_m1 = 0;
uint16_t battery_energy_buffer = 0;
uint16_t battery_energy_buffer_m1 = 0;
uint16_t battery_expected_energy_remaining = 0;
uint16_t battery_expected_energy_remaining_m1 = 0;
bool battery_full_charge_complete = false;
bool battery_fully_charged = false;
uint16_t battery_total_discharge = 0;
uint16_t battery_total_charge = 0;
uint16_t battery_BrickVoltageMax = 0;
uint16_t battery_BrickVoltageMin = 0;
uint8_t battery_BrickVoltageMaxNum = 0;
uint8_t battery_BrickVoltageMinNum = 0;
uint8_t battery_BrickTempMaxNum = 0;
uint8_t battery_BrickTempMinNum = 0;
uint8_t battery_BrickModelTMax = 0;
uint8_t battery_BrickModelTMin = 0;
uint16_t battery_packConfigMultiplexer = 0;
uint16_t battery_moduleType = 0;
uint16_t battery_reservedConfig = 0;
uint32_t battery_packMass = 0;
uint32_t battery_platformMaxBusVoltage = 0;
uint32_t battery_bms_min_voltage = 0;
uint32_t battery_bms_max_voltage = 0;
uint32_t battery_max_charge_current = 0;
uint32_t battery_max_discharge_current = 0;
uint32_t battery_soc_min = 0;
uint32_t battery_soc_max = 0;
uint32_t battery_soc_ave = 0;
uint32_t battery_soc_ui = 0;
uint8_t battery_BMS_contactorState = 0;
uint8_t battery_BMS_state = 0;
uint8_t battery_BMS_hvState = 0;
uint16_t battery_BMS_isolationResistance = 0;
uint8_t battery_BMS_uiChargeStatus = 0;
bool battery_BMS_diLimpRequest = false;
uint16_t battery_BMS_chgPowerAvailable = 0;
bool battery_BMS_pcsPwmEnabled = false;
uint8_t battery_PCS_dcdcPrechargeStatus = 0;
uint8_t battery_PCS_dcdc12VSupportStatus = 0;
uint8_t battery_PCS_dcdcHvBusDischargeStatus = 0;
uint8_t battery_PCS_dcdcMainState = 0;
uint8_t battery_PCS_dcdcSubState = 0;
bool battery_PCS_dcdcFaulted = false;
bool battery_PCS_dcdcOutputIsLimited = false;
uint16_t battery_PCS_dcdcMaxOutputCurrentAllowed = 0;
uint8_t battery_PCS_dcdcPrechargeRtyCnt = 0;
uint8_t battery_PCS_dcdc12VSupportRtyCnt = 0;
uint8_t battery_PCS_dcdcDischargeRtyCnt = 0;
uint8_t battery_PCS_dcdcPwmEnableLine = 0;
uint8_t battery_PCS_dcdcSupportingFixedLvTarget = 0;
uint8_t battery_PCS_dcdcPrechargeRestartCnt = 0;
uint8_t battery_PCS_dcdcInitialPrechargeSubState = 0;
uint16_t BMS_maxRegenPower = 0;
uint16_t BMS_maxDischargePower = 0;
uint16_t BMS_maxStationaryHeatPower = 0;
uint16_t BMS_hvacPowerBudget = 0;
uint8_t BMS_notEnoughPowerForHeatPump = 0;
uint8_t BMS_powerLimitState = 0;
uint8_t BMS_inverterTQF = 0;
uint16_t BMS_powerDissipation = 0;
uint8_t BMS_flowRequest = 0;
uint16_t BMS_inletActiveCoolTargetT = 0;
uint16_t BMS_inletPassiveTargetT = 0;
uint16_t BMS_inletActiveHeatTargetT = 0;
uint16_t BMS_packTMin = 0;
uint16_t BMS_packTMax = 0;
bool BMS_pcsNoFlowRequest = false;
bool BMS_noFlowRequest = false;
uint16_t PCS_dcdcTemp = 0;
uint16_t PCS_ambientTemp = 0;
uint16_t PCS_dcdcMaxLvOutputCurrent = 0;
uint16_t PCS_dcdcCurrentLimit = 0;
uint16_t PCS_dcdcLvOutputCurrentTempLimit = 0;
uint16_t PCS_dcdcUnifiedCommand = 0;
uint16_t PCS_dcdcCLAControllerOutput = 0;
uint16_t PCS_dcdcTankVoltage = 0;
uint16_t PCS_dcdcTankVoltageTarget = 0;
uint16_t PCS_dcdcClaCurrentFreq = 0;
uint16_t PCS_dcdcTCommMeasured = 0;
uint16_t PCS_dcdcShortTimeUs = 0;
uint16_t PCS_dcdcHalfPeriodUs = 0;
uint16_t PCS_dcdcIntervalMaxFrequency = 0;
uint16_t PCS_dcdcIntervalMaxHvBusVolt = 0;
uint16_t PCS_dcdcIntervalMaxLvBusVolt = 0;
uint16_t PCS_dcdcIntervalMaxLvOutputCurr = 0;
uint16_t PCS_dcdcIntervalMinFrequency = 0;
uint16_t PCS_dcdcIntervalMinHvBusVolt = 0;
uint16_t PCS_dcdcIntervalMinLvBusVolt = 0;
uint16_t PCS_dcdcIntervalMinLvOutputCurr = 0;
uint32_t PCS_dcdc12vSupportLifetimekWh = 0;
bool HVP_gpioPassivePyroDepl = false;
bool HVP_gpioPyroIsoEn = false;
bool HVP_gpioCpFaultIn = false;
bool HVP_gpioPackContPowerEn = false;
bool HVP_gpioHvCablesOk = false;
bool HVP_gpioHvpSelfEnable = false;
bool HVP_gpioLed = false;
bool HVP_gpioCrashSignal = false;
bool HVP_gpioShuntDataReady = false;
bool HVP_gpioFcContPosAux = false;
bool HVP_gpioFcContNegAux = false;
bool HVP_gpioBmsEout = false;
bool HVP_gpioCpFaultOut = false;
bool HVP_gpioPyroPor = false;
bool HVP_gpioShuntEn = false;
bool HVP_gpioHvpVerEn = false;
bool HVP_gpioPackCoontPosFlywheel = false;
bool HVP_gpioCpLatchEnable = false;
bool HVP_gpioPcsEnable = false;
bool HVP_gpioPcsDcdcPwmEnable = false;
bool HVP_gpioPcsChargePwmEnable = false;
bool HVP_gpioFcContPowerEnable = false;
bool HVP_gpioHvilEnable = false;
bool HVP_gpioSecDrdy = false;
uint16_t HVP_hvp1v5Ref = 0;
uint16_t HVP_shuntCurrentDebug = 0;
bool HVP_packCurrentMia = false;
bool HVP_auxCurrentMia = false;
bool HVP_currentSenseMia = false;
bool HVP_shuntRefVoltageMismatch = false;
bool HVP_shuntThermistorMia = false;
uint8_t HVP_shuntHwMia = 0;
uint16_t HVP_dcLinkVoltage = 0;
uint16_t HVP_packVoltage = 0;
uint16_t HVP_fcLinkVoltage = 0;
uint16_t HVP_packContVoltage = 0;
uint16_t HVP_packNegativeV = 0;
uint16_t HVP_packPositiveV = 0;
uint16_t HVP_pyroAnalog = 0;
uint16_t HVP_dcLinkNegativeV = 0;
uint16_t HVP_dcLinkPositiveV = 0;
uint16_t HVP_fcLinkNegativeV = 0;
uint16_t HVP_fcContCoilCurrent = 0;
uint16_t HVP_fcContVoltage = 0;
uint16_t HVP_hvilInVoltage = 0;
uint16_t HVP_hvilOutVoltage = 0;
uint16_t HVP_fcLinkPositiveV = 0;
uint16_t HVP_packContCoilCurrent = 0;
uint16_t HVP_battery12V = 0;
uint16_t HVP_shuntRefVoltageDbg = 0;
uint16_t HVP_shuntAuxCurrentDbg = 0;
uint16_t HVP_shuntBarTempDbg = 0;
uint16_t HVP_shuntAsicTempDbg = 0;
uint8_t HVP_shuntAuxCurrentStatus = 0;
uint8_t HVP_shuntBarTempStatus = 0;
uint8_t HVP_shuntAsicTempStatus = 0;
} DATALAYER_INFO_TESLA;
typedef struct {
/** uint8_t */
/** Battery info, stores raw HEX values for ASCII chars */
uint8_t BatterySerialNumber[15] = {0};
uint8_t BatteryPartNumber[7] = {0};
uint8_t BMSIDcode[8] = {0};
/** uint8_t */
/** Enum, ZE0 = 0, AZE0 = 1, ZE1 = 2 */
uint8_t LEAF_gen = 0;
/** uint16_t */
/** 77Wh per gid. LEAF specific unit */
uint16_t GIDS = 0;
/** uint16_t */
/** Max regen power in kW */
uint16_t ChargePowerLimit = 0;
/** int16_t */
/** Max charge power in kW */
int16_t MaxPowerForCharger = 0;
/** bool */
/** Interlock status */
bool Interlock = false;
/** int16_t */
/** Insulation resistance, most likely kOhm */
uint16_t Insulation = 0;
/** uint8_t */
/** battery_FAIL status */
uint8_t RelayCutRequest = 0;
/** uint8_t */
/** battery_STATUS status */
uint8_t FailsafeStatus = 0;
/** bool */
/** True if fully charged */
bool Full = false;
/** bool */
/** True if battery empty */
bool Empty = false;
/** bool */
/** Battery pack allows closing of contacors */
bool MainRelayOn = false;
/** bool */
/** True if heater exists */
bool HeatExist = false;
/** bool */
/** Heater stopped */
bool HeatingStop = false;
/** bool */
/** Heater starting */
bool HeatingStart = false;
/** bool */
/** Heat request sent*/
bool HeaterSendRequest = false;
/** bool */
/** User requesting SOH reset via WebUI*/
bool UserRequestSOHreset = false;
/** bool */
/** True if the crypto challenge response from BMS is signalling a failed attempt*/
bool challengeFailed = false;
/** uint32_t */
/** Cryptographic challenge to be solved */
uint32_t CryptoChallenge = 0;
/** uint32_t */
/** Solution for crypto challenge, MSBs */
uint32_t SolvedChallengeMSB = 0;
/** uint32_t */
/** Solution for crypto challenge, LSBs */
uint32_t SolvedChallengeLSB = 0;
} DATALAYER_INFO_NISSAN_LEAF;
typedef struct {
/** uint8_t */
/** Service disconnect switch status */
bool SDSW = 0;
/** uint8_t */
/** Pilotline status */
bool pilotline = 0;
/** uint8_t */
/** Transportation mode status */
bool transportmode = 0;
/** uint8_t */
/** Componentprotection mode status */
bool componentprotection = 0;
/** uint8_t */
/** Shutdown status */
bool shutdown_active = 0;
/** uint8_t */
/** Battery heating status */
bool battery_heating = 0;
/** uint8_t */
/** All realtime_ warnings have same enumeration, 0 = no fault, 1 = error level 1, 2 error level 2, 3 error level 3 */
uint8_t rt_overcurrent = 0;
uint8_t rt_CAN_fault = 0;
uint8_t rt_overcharge = 0;
uint8_t rt_SOC_high = 0;
uint8_t rt_SOC_low = 0;
uint8_t rt_SOC_jumping = 0;
uint8_t rt_temp_difference = 0;
uint8_t rt_cell_overtemp = 0;
uint8_t rt_cell_undertemp = 0;
uint8_t rt_battery_overvolt = 0;
uint8_t rt_battery_undervol = 0;
uint8_t rt_cell_overvolt = 0;
uint8_t rt_cell_undervol = 0;
uint8_t rt_cell_imbalance = 0;
uint8_t rt_battery_unathorized = 0;
/** uint8_t */
/** HVIL status, 0 = Init, 1 = Closed, 2 = Open!, 3 = Fault */
uint8_t HVIL = 0;
/** uint8_t */
/** 0 = HV inactive, 1 = HV active, 2 = Balancing, 3 = Extern charging, 4 = AC charging, 5 = Battery error, 6 = DC charging, 7 = init */
uint8_t BMS_mode = 0;
/** uint8_t */
/** 1 = Battery display, 4 = Battery display OK, 4 = Display battery charging, 6 = Display battery check, 7 = Fault */
uint8_t battery_diagnostic = 0;
/** uint8_t */
/** 0 = init, 1 = no open HV line detected, 2 = open HV line , 3 = fault */
uint8_t status_HV_line = 0;
/** uint8_t */
/** 0 = OK, 1 = Not OK, 0x06 = init, 0x07 = fault */
uint8_t warning_support = 0;
/** uint32_t */
/** Isolation resistance in kOhm */
uint32_t isolation_resistance = 0;
/** uint8_t */
/** 0=Init, 1=BMS intermediate circuit voltage-free (U_Zwkr < 20V), 2=BMS intermediate circuit not voltage-free (U_Zwkr >/= 25V, hysteresis), 3=Error */
uint8_t BMS_status_voltage_free = 0;
/** uint8_t */
/** 0 Component_IO, 1 Restricted_CompFkt_Isoerror_I, 2 Restricted_CompFkt_Isoerror_II, 3 Restricted_CompFkt_Interlock, 4 Restricted_CompFkt_SD, 5 Restricted_CompFkt_Performance red, 6 = No component function, 7 = Init */
uint8_t BMS_error_status = 0;
/** uint8_t */
/** 0 init, 1 closed, 2 open, 3 fault */
uint8_t BMS_Kl30c_Status = 0;
/** bool */
/** true if BMS requests error/warning light */
bool BMS_OBD_MIL = 0;
bool BMS_error_lamp_req = 0;
bool BMS_warning_lamp_req = 0;
int32_t BMS_voltage_intermediate_dV = 0;
int32_t BMS_voltage_dV = 0;
} DATALAYER_INFO_MEB;
typedef struct {
/** uint16_t */
/** Values WIP*/
uint16_t battery_soc = 0;
uint16_t battery_usable_soc = 0;
uint16_t battery_soh = 0;
uint16_t battery_pack_voltage = 0;
uint16_t battery_max_cell_voltage = 0;
uint16_t battery_min_cell_voltage = 0;
uint16_t battery_12v = 0;
uint16_t battery_avg_temp = 0;
uint16_t battery_min_temp = 0;
uint16_t battery_max_temp = 0;
uint16_t battery_max_power = 0;
uint16_t battery_interlock = 0;
uint16_t battery_kwh = 0;
uint16_t battery_current = 0;
uint16_t battery_current_offset = 0;
uint16_t battery_max_generated = 0;
uint16_t battery_max_available = 0;
uint16_t battery_current_voltage = 0;
uint16_t battery_charging_status = 0;
uint16_t battery_remaining_charge = 0;
uint16_t battery_balance_capacity_total = 0;
uint16_t battery_balance_time_total = 0;
uint16_t battery_balance_capacity_sleep = 0;
uint16_t battery_balance_time_sleep = 0;
uint16_t battery_balance_capacity_wake = 0;
uint16_t battery_balance_time_wake = 0;
uint16_t battery_bms_state = 0;
uint16_t battery_balance_switches = 0;
uint16_t battery_energy_complete = 0;
uint16_t battery_energy_partial = 0;
uint16_t battery_slave_failures = 0;
uint16_t battery_mileage = 0;
uint16_t battery_fan_speed = 0;
uint16_t battery_fan_period = 0;
uint16_t battery_fan_control = 0;
uint16_t battery_fan_duty = 0;
uint16_t battery_temporisation = 0;
uint16_t battery_time = 0;
uint16_t battery_pack_time = 0;
uint16_t battery_soc_min = 0;
uint16_t battery_soc_max = 0;
} DATALAYER_INFO_ZOE_PH2;
class DataLayerExtended {
public:
DATALAYER_INFO_BOLTAMPERA boltampera;
DATALAYER_INFO_BMWIX bmwix;
DATALAYER_INFO_BMWI3 bmwi3;
DATALAYER_INFO_BYDATTO3 bydAtto3;
DATALAYER_INFO_CELLPOWER cellpower;
DATALAYER_INFO_TESLA tesla;
DATALAYER_INFO_NISSAN_LEAF nissanleaf;
DATALAYER_INFO_MEB meb;
DATALAYER_INFO_ZOE_PH2 zoePH2;
};
extern DataLayerExtended datalayer_extended;
#endif

View file

@ -7,8 +7,8 @@
#include "hw_lilygo.h"
#elif defined(HW_STARK)
#include "hw_stark.h"
#elif defined(HW_SJB_V1)
#include "hw_sjb_v1.h"
#elif defined(HW_3LB)
#include "hw_3LB.h"
#endif
#endif

View file

@ -0,0 +1,106 @@
#ifndef __HW_3LB_H__
#define __HW_3LB_H__
// Board boot-up time
#define BOOTUP_TIME 1000 // Time in ms it takes before system is considered fully started up
// Core assignment
#define CORE_FUNCTION_CORE 1
#define MODBUS_CORE 0
#define WIFI_CORE 0
// RS485
//#define PIN_5V_EN 16
//#define RS485_EN_PIN 17 // 17 /RE
#define RS485_TX_PIN 1 // 21
#define RS485_RX_PIN 3 // 22
//#define RS485_SE_PIN 19 // 22 /SHDN
// CAN settings. CAN_2 is not defined as it can be either MCP2515 or MCP2517, defined by the user settings
#define CAN_1_TYPE ESP32CAN
// CAN1 PIN mappings, do not change these unless you are adding on extra hardware to the PCB
#define CAN_TX_PIN GPIO_NUM_27
#define CAN_RX_PIN GPIO_NUM_26
//#define CAN_SE_PIN 23
// CAN2 defines below
// CAN_ADDON defines
#define MCP2515_SCK 12 // SCK input of MCP2515
#define MCP2515_MOSI 5 // SDI input of MCP2515
#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
#define MCP2515_CS 18 // CS input of MCP2515
#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
// CANFD_ADDON defines
#define MCP2517_SCK 17 // SCK input of MCP2517
#define MCP2517_SDI 23 // SDI input of MCP2517
#define MCP2517_SDO 39 // SDO output of MCP2517
#define MCP2517_CS 21 // CS input of MCP2517 //21 or 22
#define MCP2517_INT 34 // INT output of MCP2517 //34 or 35
// CHAdeMO support pin dependencies
#define CHADEMO_PIN_2 12
#define CHADEMO_PIN_10 5
#define CHADEMO_PIN_7 34
#define CHADEMO_PIN_4 35
#define CHADEMO_LOCK 18
// Contactor handling
#define POSITIVE_CONTACTOR_PIN 32
#define NEGATIVE_CONTACTOR_PIN 33
#define PRECHARGE_PIN 25
#define SECOND_POSITIVE_CONTACTOR_PIN 13
#define SECOND_NEGATIVE_CONTACTOR_PIN 16
#define SECOND_PRECHARGE_PIN 18
// SMA CAN contactor pins
#define INVERTER_CONTACTOR_ENABLE_PIN 36
// SD card
//#define SD_MISO_PIN 2
//#define SD_MOSI_PIN 15
//#define SD_SCLK_PIN 14
//#define SD_CS_PIN 13
// LED
#define LED_PIN 4
#define LED_MAX_BRIGHTNESS 40
// Equipment stop pin
#define EQUIPMENT_STOP_PIN 35
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */
#ifndef HW_CONFIGURED
#define HW_CONFIGURED
#else
#error Multiple HW defined! Please select a single HW
#endif
#ifdef CHADEMO_BATTERY
#ifdef CAN_ADDON
#error CHADEMO and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#ifdef EQUIPMENT_STOP_BUTTON
#ifdef CAN_ADDON
#error EQUIPMENT_STOP_BUTTON and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CANFD_ADDON
#error EQUIPMENT_STOP_BUTTON and CANFD_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CHADEMO_BATTERY
#error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#ifdef BMW_I3_BATTERY
#ifdef CONTACTOR_CONTROL
#error GPIO PIN 25 cannot be used for both BMWi3 Wakeup and contactor control. Disable CONTACTOR_CONTROL
#endif
#endif
#endif

View file

@ -26,14 +26,14 @@
// CAN2 defines below
// DUAL_CAN defines
// CAN_ADDON defines
#define MCP2515_SCK 12 // SCK input of MCP2515
#define MCP2515_MOSI 5 // SDI input of MCP2515
#define MCP2515_MISO 34 // SDO output of MCP2515 | Pin 34 is input only, without pullup/down resistors
#define MCP2515_CS 18 // CS input of MCP2515
#define MCP2515_INT 35 // INT output of MCP2515 | | Pin 35 is input only, without pullup/down resistors
// CAN_FD defines
// CANFD_ADDON defines
#define MCP2517_SCK 12 // SCK input of MCP2517
#define MCP2517_SDI 5 // SDI input of MCP2517
#define MCP2517_SDO 34 // SDO output of MCP2517
@ -65,6 +65,9 @@
#define LED_PIN 4
#define LED_MAX_BRIGHTNESS 40
// Equipment stop pin
#define EQUIPMENT_STOP_PIN 35
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */
#ifndef HW_CONFIGURED
#define HW_CONFIGURED
@ -73,8 +76,20 @@
#endif
#ifdef CHADEMO_BATTERY
#ifdef DUAL_CAN
#error CHADEMO and DUAL_CAN cannot coexist due to overlapping GPIO pin usage
#ifdef CAN_ADDON
#error CHADEMO and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#endif
#ifdef EQUIPMENT_STOP_BUTTON
#ifdef CAN_ADDON
#error EQUIPMENT_STOP_BUTTON and CAN_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CANFD_ADDON
#error EQUIPMENT_STOP_BUTTON and CANFD_ADDON cannot coexist due to overlapping GPIO pin usage
#endif
#ifdef CHADEMO_BATTERY
#error EQUIPMENT_STOP_BUTTON and CHADEMO_BATTERY cannot coexist due to overlapping GPIO pin usage
#endif
#endif

View file

@ -38,7 +38,7 @@ GPIOs on extra header
#define CAN_RX_PIN GPIO_NUM_26
// #define CAN_SE_PIN 23 // (No function, GPIO 23 used instead as MCP_SCK)
// CAN_FD defines
// CANFD_ADDON defines
#define MCP2517_SCK 17 // SCK input of MCP2517
#define MCP2517_SDI 5 // SDI input of MCP2517
#define MCP2517_SDO 34 // SDO output of MCP2517
@ -58,6 +58,9 @@ GPIOs on extra header
#define LED_PIN 4
#define LED_MAX_BRIGHTNESS 40
// Equipment stop pin
#define EQUIPMENT_STOP_PIN 2
/* ----- Error checks below, don't change (can't be moved to separate file) ----- */
#ifndef HW_CONFIGURED
#define HW_CONFIGURED

View file

@ -2,6 +2,7 @@
#include <Arduino.h>
#include <WiFi.h>
#include <freertos/FreeRTOS.h>
#include "../../../USER_SECRETS.h"
#include "../../../USER_SETTINGS.h"
#include "../../battery/BATTERIES.h"
#include "../../datalayer/datalayer.h"
@ -13,9 +14,18 @@
WiFiClient espClient;
PubSubClient client(espClient);
char mqtt_msg[MQTT_MSG_BUFFER_SIZE];
static unsigned long previousMillisUpdateVal;
MyTimer publish_global_timer(5000);
static const char* hostname = WiFi.getHostname();
MyTimer publish_global_timer(5000); //publish timer
MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical.
static String topic_name = "";
static String object_id_prefix = "";
static String device_name = "";
// Tracking reconnection attempts and failures
static unsigned long lastReconnectAttempt = 0;
static uint8_t reconnectAttempts = 0;
static const uint8_t maxReconnectAttempts = 5;
static bool connected_once = false;
static void publish_common_info(void);
static void publish_cell_voltages(void);
@ -38,49 +48,70 @@ struct SensorConfig {
};
SensorConfig sensorConfigs[] = {
{"SOC", "Battery Emulator SOC (scaled)", "{{ value_json.SOC }}", "%", "battery"},
{"SOC_real", "Battery Emulator SOC (real)", "{{ value_json.SOC_real }}", "%", "battery"},
{"state_of_health", "Battery Emulator State Of Health", "{{ value_json.state_of_health }}", "%", "battery"},
{"temperature_min", "Battery Emulator Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"},
{"temperature_max", "Battery Emulator Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"},
{"stat_batt_power", "Battery Emulator Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"},
{"battery_current", "Battery Emulator Battery Current", "{{ value_json.battery_current }}", "A", "current"},
{"cell_max_voltage", "Battery Emulator Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"},
{"cell_min_voltage", "Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"},
{"battery_voltage", "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"},
{"total_capacity", "Battery Emulator Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"},
{"remaining_capacity", "Battery Emulator Battery Remaining Capacity", "{{ value_json.remaining_capacity }}", "Wh",
{"SOC", "SOC (scaled)", "{{ value_json.SOC }}", "%", "battery"},
{"SOC_real", "SOC (real)", "{{ value_json.SOC_real }}", "%", "battery"},
{"state_of_health", "State Of Health", "{{ value_json.state_of_health }}", "%", "battery"},
{"temperature_min", "Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"},
{"temperature_max", "Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"},
{"stat_batt_power", "Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"},
{"battery_current", "Battery Current", "{{ value_json.battery_current }}", "A", "current"},
{"cell_max_voltage", "Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"},
{"cell_min_voltage", "Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"},
{"battery_voltage", "Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"},
{"total_capacity", "Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"},
{"remaining_capacity", "Battery Remaining Capacity (scaled)", "{{ value_json.remaining_capacity }}", "Wh",
"energy"},
{"max_discharge_power", "Battery Emulator Battery Max Discharge Power", "{{ value_json.max_discharge_power }}", "W",
"power"},
{"max_charge_power", "Battery Emulator Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W",
"power"},
{"bms_status", "Battery Emulator BMS Status", "{{ value_json.bms_status }}", "", ""},
{"pause_status", "Battery Emulator Pause Status", "{{ value_json.pause_status }}", "", ""},
{"remaining_capacity_real", "Battery Remaining Capacity (real)", "{{ value_json.remaining_capacity_real }}", "Wh",
"energy"},
{"max_discharge_power", "Battery Max Discharge Power", "{{ value_json.max_discharge_power }}", "W", "power"},
{"max_charge_power", "Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", "power"},
{"bms_status", "BMS Status", "{{ value_json.bms_status }}", "", ""},
{"pause_status", "Pause Status", "{{ value_json.pause_status }}", "", ""},
#ifdef DOUBLE_BATTERY
{"SOC_2", "SOC 2 (scaled)", "{{ value_json.SOC_2 }}", "%", "battery"},
{"SOC_real_2", "SOC 2 (real)", "{{ value_json.SOC_real_2 }}", "%", "battery"},
{"state_of_health_2", "State Of Health 2", "{{ value_json.state_of_health_2 }}", "%", "battery"},
{"temperature_min_2", "Temperature Min 2", "{{ value_json.temperature_min_2 }}", "°C", "temperature"},
{"temperature_max_2", "Temperature Max 2", "{{ value_json.temperature_max_2 }}", "°C", "temperature"},
{"stat_batt_power_2", "Stat Batt Power 2", "{{ value_json.stat_batt_power_2 }}", "W", "power"},
{"battery_current_2", "Battery 2 Current", "{{ value_json.battery_current_2 }}", "A", "current"},
{"cell_max_voltage_2", "Cell Max Voltage 2", "{{ value_json.cell_max_voltage_2 }}", "V", "voltage"},
{"cell_min_voltage_2", "Cell Min Voltage 2", "{{ value_json.cell_min_voltage_2 }}", "V", "voltage"},
{"battery_voltage_2", "Battery 2 Voltage", "{{ value_json.battery_voltage_2 }}", "V", "voltage"},
{"total_capacity_2", "Battery 2 Total Capacity", "{{ value_json.total_capacity_2 }}", "Wh", "energy"},
{"remaining_capacity_2", "Battery 2 Remaining Capacity (scaled)", "{{ value_json.remaining_capacity_2 }}", "Wh",
"energy"},
{"remaining_capacity_real_2", "Battery 2 Remaining Capacity (real)", "{{ value_json.remaining_capacity_real_2 }}",
"Wh", "energy"},
{"max_discharge_power_2", "Battery 2 Max Discharge Power", "{{ value_json.max_discharge_power_2 }}", "W", "power"},
{"max_charge_power_2", "Battery 2 Max Charge Power", "{{ value_json.max_charge_power_2 }}", "W", "power"},
{"bms_status_2", "BMS 2 Status", "{{ value_json.bms_status_2 }}", "", ""},
{"pause_status_2", "Pause Status 2", "{{ value_json.pause_status_2 }}", "", ""},
#endif // DOUBLE_BATTERY
};
static String generateCommonInfoAutoConfigTopic(const char* object_id, const char* hostname) {
return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/" + String(object_id) + "/config";
static String generateCommonInfoAutoConfigTopic(const char* object_id) {
return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config";
}
static String generateCellVoltageAutoConfigTopic(int cell_number, const char* hostname) {
return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/cell_voltage" + String(cell_number) +
"/config";
static String generateCellVoltageAutoConfigTopic(int cell_number, String battery_suffix) {
return "homeassistant/sensor/" + topic_name + "/cell_voltage" + battery_suffix + String(cell_number) + "/config";
}
static String generateEventsAutoConfigTopic(const char* object_id, const char* hostname) {
return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/" + String(object_id) + "/config";
static String generateEventsAutoConfigTopic(const char* object_id) {
return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config";
}
#endif // HA_AUTODISCOVERY
static std::vector<EventData> order_events;
static void publish_common_info(void) {
static JsonDocument doc;
#ifdef HA_AUTODISCOVERY
static bool mqtt_first_transmission = true;
#endif // HA_AUTODISCOVERY
static String state_topic = String("battery-emulator_") + String(hostname) + "/info";
static String state_topic = topic_name + "/info";
#ifdef HA_AUTODISCOVERY
if (mqtt_first_transmission == true) {
mqtt_first_transmission = false;
@ -88,8 +119,8 @@ static void publish_common_info(void) {
SensorConfig& config = sensorConfigs[i];
doc["name"] = config.name;
doc["state_topic"] = state_topic;
doc["unique_id"] = "battery-emulator_" + String(hostname) + "_" + String(config.object_id);
doc["object_id"] = String(hostname) + "_" + String(config.object_id);
doc["unique_id"] = topic_name + "_" + String(config.object_id);
doc["object_id"] = object_id_prefix + String(config.object_id);
doc["value_template"] = config.value_template;
if (config.unit != nullptr && strlen(config.unit) > 0)
doc["unit_of_measurement"] = config.unit;
@ -102,12 +133,12 @@ static void publish_common_info(void) {
doc["device"]["identifiers"][0] = "battery-emulator";
doc["device"]["manufacturer"] = "DalaTech";
doc["device"]["model"] = "BatteryEmulator";
doc["device"]["name"] = "BatteryEmulator_" + String(hostname);
doc["device"]["name"] = device_name;
doc["origin"]["name"] = "BatteryEmulator";
doc["origin"]["sw"] = String(version_number) + "-mqtt";
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
serializeJson(doc, mqtt_msg);
mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id, hostname).c_str(), mqtt_msg, true);
mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true);
doc.clear();
}
@ -117,7 +148,7 @@ static void publish_common_info(void) {
doc["pause_status"] = get_emulator_pause_status();
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
if (datalayer.battery.status.bms_status == ACTIVE && can_send_CAN && millis() > BOOTUP_TIME) {
if (datalayer.battery.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) {
doc["SOC"] = ((float)datalayer.battery.status.reported_soc) / 100.0;
doc["SOC_real"] = ((float)datalayer.battery.status.real_soc) / 100.0;
doc["state_of_health"] = ((float)datalayer.battery.status.soh_pptt) / 100.0;
@ -133,16 +164,40 @@ static void publish_common_info(void) {
doc["cell_min_voltage"] = ((float)datalayer.battery.status.cell_min_voltage_mV) / 1000.0;
}
doc["total_capacity"] = ((float)datalayer.battery.info.total_capacity_Wh);
doc["remaining_capacity"] = ((float)datalayer.battery.status.remaining_capacity_Wh);
doc["remaining_capacity_real"] = ((float)datalayer.battery.status.remaining_capacity_Wh);
doc["remaining_capacity"] = ((float)datalayer.battery.status.reported_remaining_capacity_Wh);
doc["max_discharge_power"] = ((float)datalayer.battery.status.max_discharge_power_W);
doc["max_charge_power"] = ((float)datalayer.battery.status.max_charge_power_W);
}
#ifdef DOUBLE_BATTERY
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
if (datalayer.battery2.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) {
doc["SOC_2"] = ((float)datalayer.battery2.status.reported_soc) / 100.0;
doc["SOC_real_2"] = ((float)datalayer.battery2.status.real_soc) / 100.0;
doc["state_of_health_2"] = ((float)datalayer.battery2.status.soh_pptt) / 100.0;
doc["temperature_min_2"] = ((float)((int16_t)datalayer.battery2.status.temperature_min_dC)) / 10.0;
doc["temperature_max_2"] = ((float)((int16_t)datalayer.battery2.status.temperature_max_dC)) / 10.0;
doc["stat_batt_power_2"] = ((float)((int32_t)datalayer.battery2.status.active_power_W));
doc["battery_current_2"] = ((float)((int16_t)datalayer.battery2.status.current_dA)) / 10.0;
doc["battery_voltage_2"] = ((float)datalayer.battery2.status.voltage_dV) / 10.0;
// publish only if cell voltages have been populated...
if (datalayer.battery2.info.number_of_cells != 0u &&
datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) {
doc["cell_max_voltage_2"] = ((float)datalayer.battery2.status.cell_max_voltage_mV) / 1000.0;
doc["cell_min_voltage_2"] = ((float)datalayer.battery2.status.cell_min_voltage_mV) / 1000.0;
}
doc["total_capacity_2"] = ((float)datalayer.battery2.info.total_capacity_Wh);
doc["remaining_capacity_real_2"] = ((float)datalayer.battery2.status.remaining_capacity_Wh);
doc["remaining_capacity_2"] = ((float)datalayer.battery2.status.reported_remaining_capacity_Wh);
doc["max_discharge_power_2"] = ((float)datalayer.battery2.status.max_discharge_power_W);
doc["max_charge_power_2"] = ((float)datalayer.battery2.status.max_charge_power_W);
}
#endif // DOUBLE_BATTERY
serializeJson(doc, mqtt_msg);
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
#ifdef DEBUG_VIA_USB
Serial.println("Common info MQTT msg could not be sent");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("Common info MQTT msg could not be sent");
#endif // DEBUG_LOG
}
doc.clear();
#ifdef HA_AUTODISCOVERY
@ -155,49 +210,80 @@ static void publish_cell_voltages(void) {
static bool mqtt_first_transmission = true;
#endif // HA_AUTODISCOVERY
static JsonDocument doc;
static String state_topic = String("battery-emulator_") + String(hostname) + "/spec_data";
static String state_topic = topic_name + "/spec_data";
#ifdef DOUBLE_BATTERY
static String state_topic_2 = topic_name + "/spec_data_2";
#endif // DOUBLE_BATTERY
// If the cell voltage number isn't initialized...
if (datalayer.battery.info.number_of_cells == 0u) {
return;
}
#ifdef HA_AUTODISCOVERY
if (mqtt_first_transmission == true) {
mqtt_first_transmission = false;
String topic = "homeassistant/sensor/battery-emulator/cell_voltage";
for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
int cellNumber = i + 1;
doc["name"] = "Battery Cell Voltage " + String(cellNumber);
doc["object_id"] = "battery_voltage_cell" + String(cellNumber);
doc["unique_id"] = "battery-emulator_" + String(hostname) + "_battery_voltage_cell" +
String(cellNumber); //"battery-emulator_" + String(hostname) + "_" +
doc["device_class"] = "voltage";
doc["state_class"] = "measurement";
doc["state_topic"] = state_topic;
doc["unit_of_measurement"] = "V";
doc["enabled_by_default"] = true;
doc["expire_after"] = 240;
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
doc["device"]["identifiers"][0] = "battery-emulator";
doc["device"]["manufacturer"] = "DalaTech";
doc["device"]["model"] = "BatteryEmulator";
doc["device"]["name"] = "BatteryEmulator_" + String(hostname);
doc["origin"]["name"] = "BatteryEmulator";
doc["origin"]["sw"] = String(version_number) + "-mqtt";
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
// If the cell voltage number isn't initialized...
if (datalayer.battery.info.number_of_cells != 0u) {
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, hostname).c_str(), mqtt_msg, true);
for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
int cellNumber = i + 1;
doc["name"] = "Battery Cell Voltage " + String(cellNumber);
doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber);
doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber);
doc["device_class"] = "voltage";
doc["state_class"] = "measurement";
doc["state_topic"] = state_topic;
doc["unit_of_measurement"] = "V";
doc["enabled_by_default"] = true;
doc["expire_after"] = 240;
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
doc["device"]["identifiers"][0] = "battery-emulator";
doc["device"]["manufacturer"] = "DalaTech";
doc["device"]["model"] = "BatteryEmulator";
doc["device"]["name"] = device_name;
doc["origin"]["name"] = "BatteryEmulator";
doc["origin"]["sw"] = String(version_number) + "-mqtt";
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true);
}
doc.clear(); // clear after sending autoconfig
}
doc.clear(); // clear after sending autoconfig
} else {
#ifdef DOUBLE_BATTERY
// If the cell voltage number isn't initialized...
if (datalayer.battery2.info.number_of_cells != 0u) {
for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
int cellNumber = i + 1;
doc["name"] = "Battery 2 Cell Voltage " + String(cellNumber);
doc["object_id"] = object_id_prefix + "battery_2_voltage_cell" + String(cellNumber);
doc["unique_id"] = topic_name + "_battery_2_voltage_cell" + String(cellNumber);
doc["device_class"] = "voltage";
doc["state_class"] = "measurement";
doc["state_topic"] = state_topic_2;
doc["unit_of_measurement"] = "V";
doc["enabled_by_default"] = true;
doc["expire_after"] = 240;
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
doc["device"]["identifiers"][0] = "battery-emulator";
doc["device"]["manufacturer"] = "DalaTech";
doc["device"]["model"] = "BatteryEmulator";
doc["device"]["name"] = device_name;
doc["origin"]["name"] = "BatteryEmulator";
doc["origin"]["sw"] = String(version_number) + "-mqtt";
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true);
}
doc.clear(); // clear after sending autoconfig
}
#endif // DOUBLE_BATTERY
}
#endif // HA_AUTODISCOVERY
// If cell voltages haven't been populated...
if (datalayer.battery.info.number_of_cells == 0u ||
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] == 0u) {
return;
}
// If cell voltages have been populated...
if (datalayer.battery.info.number_of_cells != 0u &&
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) {
JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>();
for (size_t i = 0; i < datalayer.battery.info.number_of_cells; ++i) {
@ -207,14 +293,33 @@ static void publish_cell_voltages(void) {
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
#ifdef DEBUG_VIA_USB
Serial.println("Cell voltage MQTT msg could not be sent");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("Cell voltage MQTT msg could not be sent");
#endif // DEBUG_LOG
}
doc.clear();
#ifdef HA_AUTODISCOVERY
}
#endif // HA_AUTODISCOVERY
#ifdef DOUBLE_BATTERY
// If cell voltages have been populated...
if (datalayer.battery2.info.number_of_cells != 0u &&
datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) {
JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>();
for (size_t i = 0; i < datalayer.battery2.info.number_of_cells; ++i) {
cell_voltages.add(((float)datalayer.battery2.status.cell_voltages_mV[i]) / 1000.0);
}
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
if (!mqtt_publish(state_topic_2.c_str(), mqtt_msg, false)) {
#ifdef DEBUG_LOG
logging.println("Cell voltage MQTT msg could not be sent");
#endif // DEBUG_LOG
}
doc.clear();
}
#endif // DOUBLE_BATTERY
}
void publish_events() {
@ -223,17 +328,17 @@ void publish_events() {
#ifdef HA_AUTODISCOVERY
static bool mqtt_first_transmission = true;
#endif // HA_AUTODISCOVERY
static String state_topic = String("battery-emulator_") + String(hostname) + "/events";
static String state_topic = topic_name + "/events";
#ifdef HA_AUTODISCOVERY
if (mqtt_first_transmission == true) {
mqtt_first_transmission = false;
doc["name"] = "Battery Emulator Event";
doc["name"] = "Event";
doc["state_topic"] = state_topic;
doc["unique_id"] = "battery-emulator_" + String(hostname) + "_event";
doc["object_id"] = String(hostname) + "_event";
doc["unique_id"] = topic_name + "_event";
doc["object_id"] = object_id_prefix + "event";
doc["value_template"] =
"{{ value_json.event_type ~ ' (c:' ~ value_json.count ~ ',m:' ~ value_json.milis ~ ') ' ~ value_json.message "
"{{ value_json.event_type ~ ' (c:' ~ value_json.count ~ ',m:' ~ value_json.millis ~ ') ' ~ value_json.message "
"}}";
doc["json_attributes_topic"] = state_topic;
doc["json_attributes_template"] = "{{ value_json | tojson }}";
@ -241,98 +346,146 @@ void publish_events() {
doc["device"]["identifiers"][0] = "battery-emulator";
doc["device"]["manufacturer"] = "DalaTech";
doc["device"]["model"] = "BatteryEmulator";
doc["device"]["name"] = "BatteryEmulator_" + String(hostname);
doc["device"]["name"] = device_name;
doc["origin"]["name"] = "BatteryEmulator";
doc["origin"]["sw"] = String(version_number) + "-mqtt";
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
serializeJson(doc, mqtt_msg);
mqtt_publish(generateEventsAutoConfigTopic("event", hostname).c_str(), mqtt_msg, true);
mqtt_publish(generateEventsAutoConfigTopic("event").c_str(), mqtt_msg, true);
doc.clear();
} else {
#endif // HA_AUTODISCOVERY
const EVENTS_STRUCT_TYPE* event_pointer;
unsigned long timestamp_now = get_current_event_time_secs();
//clear the vector
order_events.clear();
// Collect all events
for (int i = 0; i < EVENT_NOF_EVENTS; i++) {
event_pointer = get_event_pointer((EVENTS_ENUM_TYPE)i);
EVENTS_ENUM_TYPE event_handle = static_cast<EVENTS_ENUM_TYPE>(i);
if (event_pointer->occurences > 0 && !event_pointer->MQTTpublished) {
doc["event_type"] = String(get_event_enum_string(event_handle));
doc["severity"] = String(get_event_level_string(event_handle));
time_t time_difference = timestamp_now - event_pointer->timestamp;
doc["last_event"] = String(timestamp_now - event_pointer->timestamp);
doc["count"] = String(event_pointer->occurences);
doc["data"] = String(event_pointer->data);
doc["message"] = String(get_event_message_string(event_handle));
doc["milis"] = String(millis());
serializeJson(doc, mqtt_msg);
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
#ifdef DEBUG_VIA_USB
Serial.println("Common info MQTT msg could not be sent");
#endif // DEBUG_VIA_USB
} else {
set_event_MQTTpublished(event_handle);
}
doc.clear();
order_events.push_back({static_cast<EVENTS_ENUM_TYPE>(i), event_pointer});
}
}
// Sort events by timestamp
std::sort(order_events.begin(), order_events.end(), compareEventsByTimestampAsc);
for (const auto& event : order_events) {
EVENTS_ENUM_TYPE event_handle = event.event_handle;
event_pointer = event.event_pointer;
doc["event_type"] = String(get_event_enum_string(event_handle));
doc["severity"] = String(get_event_level_string(event_handle));
doc["count"] = String(event_pointer->occurences);
doc["data"] = String(event_pointer->data);
doc["message"] = String(get_event_message_string(event_handle));
doc["millis"] = String(event_pointer->timestamp);
serializeJson(doc, mqtt_msg);
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
#ifdef DEBUG_LOG
logging.println("Common info MQTT msg could not be sent");
#endif // DEBUG_LOG
} else {
set_event_MQTTpublished(event_handle);
}
doc.clear();
//clear the vector
order_events.clear();
}
#ifdef HA_AUTODISCOVERY
}
#endif // HA_AUTODISCOVERY
}
/* If we lose the connection, get it back */
static void reconnect() {
static bool reconnect() {
// attempt one reconnection
#ifdef DEBUG_VIA_USB
Serial.print("Attempting MQTT connection... ");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.print("Attempting MQTT connection... ");
#endif // DEBUG_LOG
char clientId[64]; // Adjust the size as needed
snprintf(clientId, sizeof(clientId), "LilyGoClient-%s", hostname);
snprintf(clientId, sizeof(clientId), "BatteryEmulatorClient-%s", WiFi.getHostname());
// Attempt to connect
if (client.connect(clientId, mqtt_user, mqtt_password)) {
#ifdef DEBUG_VIA_USB
Serial.println("connected");
#endif // DEBUG_VIA_USB
connected_once = true;
clear_event(EVENT_MQTT_DISCONNECT);
set_event(EVENT_MQTT_CONNECT, 0);
reconnectAttempts = 0; // Reset attempts on successful connection
#ifdef DEBUG_LOG
logging.println("connected");
#endif // DEBUG_LOG
clear_event(EVENT_MQTT_CONNECT);
} else {
#ifdef DEBUG_VIA_USB
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
#endif // DEBUG_VIA_USB
if (connected_once)
set_event(EVENT_MQTT_DISCONNECT, 0);
reconnectAttempts++; // Count failed attempts
#ifdef DEBUG_LOG
logging.print("failed, rc=");
logging.print(client.state());
logging.println(" try again in 5 seconds");
#endif // DEBUG_LOG
// Wait 5 seconds before retrying
}
return client.connected();
}
void init_mqtt(void) {
client.setServer(MQTT_SERVER, MQTT_PORT);
#ifdef DEBUG_VIA_USB
Serial.println("MQTT initialized");
#endif // DEBUG_VIA_USB
previousMillisUpdateVal = millis();
#ifdef MQTT
#ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME
// Use custom topic name, object ID prefix, and device name from user settings
topic_name = mqtt_topic_name;
object_id_prefix = mqtt_object_id_prefix;
device_name = mqtt_device_name;
#else
// Use default naming based on WiFi hostname for topic, object ID prefix, and device name
topic_name = "battery-emulator_" + String(WiFi.getHostname());
object_id_prefix = String(WiFi.getHostname()) + String("_");
device_name = "BatteryEmulator_" + String(WiFi.getHostname());
#endif
#endif
client.setServer(MQTT_SERVER, MQTT_PORT);
#ifdef DEBUG_LOG
logging.println("MQTT initialized");
#endif // DEBUG_LOG
client.setKeepAlive(30); // Increase keepalive to manage network latency better. default is 15
lastReconnectAttempt = millis();
reconnect();
}
void mqtt_loop(void) {
if (client.connected()) {
client.loop();
if (publish_global_timer.elapsed() == true) // Every 5s
{
publish_values();
}
} else {
if (millis() - previousMillisUpdateVal >= 5000) // Every 5s
{
previousMillisUpdateVal = millis();
reconnect();
// Only attempt to publish/reconnect MQTT if Wi-Fi is connectedand checkTimmer is elapsed
if (check_global_timer.elapsed() && WiFi.status() == WL_CONNECTED) {
if (client.connected()) {
client.loop();
if (publish_global_timer.elapsed()) // Every 5s
{
publish_values();
}
} else {
if (connected_once)
set_event(EVENT_MQTT_DISCONNECT, 0);
unsigned long now = millis();
if (now - lastReconnectAttempt >= 5000) // Every 5s
{
lastReconnectAttempt = now;
if (reconnect()) {
lastReconnectAttempt = 0;
} else if (reconnectAttempts >= maxReconnectAttempts) {
#ifdef DEBUG_LOG
logging.println("Too many failed reconnect attempts, restarting client.");
#endif
client.disconnect(); // Force close the MQTT client connection
reconnectAttempts = 0; // Reset attempts to avoid infinite loop
}
}
}
}
}
@ -341,5 +494,8 @@ bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain) {
if (client.connected() == true) {
return client.publish(topic, mqtt_msg, retain);
}
if (connected_once)
set_event(EVENT_MQTT_DISCONNECT, 0);
return false;
}

View file

@ -35,6 +35,7 @@
#define __MQTT_H__
#include <Arduino.h>
#include <vector>
#include "../../include.h"
#define MQTT_MSG_BUFFER_SIZE (1024)
@ -43,6 +44,9 @@ extern const char* version_number; // The current software version, used for mq
extern const char* mqtt_user;
extern const char* mqtt_password;
extern const char* mqtt_topic_name;
extern const char* mqtt_object_id_prefix;
extern const char* mqtt_device_name;
extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE];

View file

@ -12,7 +12,7 @@ static bool battery_empty_event_fired = false;
//battery pause status begin
bool emulator_pause_request_ON = false;
bool emulator_pause_CAN_send_ON = false;
bool can_send_CAN = true;
bool allowed_to_send_CAN = true;
battery_pause_status emulator_pause_status = NORMAL;
//battery pause status end
@ -27,14 +27,14 @@ void update_machineryprotection() {
}
// Battery is overheated!
if (datalayer.battery.status.temperature_max_dC > 500) {
if (datalayer.battery.status.temperature_max_dC > BATTERY_MAXTEMPERATURE) {
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
} else {
clear_event(EVENT_BATTERY_OVERHEAT);
}
// Battery is frozen!
if (datalayer.battery.status.temperature_min_dC < -250) {
if (datalayer.battery.status.temperature_min_dC < BATTERY_MINTEMPERATURE) {
set_event(EVENT_BATTERY_FROZEN, datalayer.battery.status.temperature_min_dC);
} else {
clear_event(EVENT_BATTERY_FROZEN);
@ -54,6 +54,15 @@ void update_machineryprotection() {
clear_event(EVENT_BATTERY_UNDERVOLTAGE);
}
// Cell overvoltage, critical latching error without automatic reset. Requires user action.
if (datalayer.battery.status.cell_max_voltage_mV >= datalayer.battery.info.max_cell_voltage_mV) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
// Cell undervoltage, critical latching error without automatic reset. Requires user action.
if (datalayer.battery.status.cell_min_voltage_mV <= datalayer.battery.info.min_cell_voltage_mV) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
// Battery is fully charged. Dont allow any more power into it
// Normally the BMS will send 0W allowed, but this acts as an additional layer of safety
if (datalayer.battery.status.reported_soc == 10000) //Scaled SOC% value is 100.00%
@ -88,6 +97,7 @@ void update_machineryprotection() {
clear_event(EVENT_SOH_LOW);
}
#ifdef NISSAN_LEAF_BATTERY
// Check if SOC% is plausible
if (datalayer.battery.status.voltage_dV >
(datalayer.battery.info.max_design_voltage_dV -
@ -98,10 +108,12 @@ void update_machineryprotection() {
clear_event(EVENT_SOC_PLAUSIBILITY_ERROR);
}
}
#endif //NISSAN_LEAF_BATTERY
// Check diff between highest and lowest cell
cell_deviation_mV = (datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
cell_deviation_mV =
std::abs(datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV);
if (cell_deviation_mV > datalayer.battery.info.max_cell_voltage_deviation_mV) {
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
@ -150,6 +162,16 @@ void update_machineryprotection() {
clear_event(EVENT_CAN_RX_WARNING);
}
#ifdef CAN_INVERTER_SELECTED
// Check if the inverter is still sending CAN messages. If we go 60s without messages we raise an error
if (!datalayer.system.status.CAN_inverter_still_alive) {
set_event(EVENT_CAN_INVERTER_MISSING, 0);
} else {
datalayer.system.status.CAN_inverter_still_alive--;
clear_event(EVENT_CAN_INVERTER_MISSING);
}
#endif //CAN_INVERTER_SELECTED
#ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here
// Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise an error
@ -173,6 +195,23 @@ void update_machineryprotection() {
clear_event(EVENT_CAN_RX_WARNING);
}
// Cell overvoltage, critical latching error without automatic reset. Requires user action.
if (datalayer.battery2.status.cell_max_voltage_mV >= datalayer.battery2.info.max_cell_voltage_mV) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
// Cell undervoltage, critical latching error without automatic reset. Requires user action.
if (datalayer.battery2.status.cell_min_voltage_mV <= datalayer.battery2.info.min_cell_voltage_mV) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
// Check diff between highest and lowest cell
cell_deviation_mV = (datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV);
if (cell_deviation_mV > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
set_event(EVENT_CELL_DEVIATION_HIGH, (cell_deviation_mV / 20));
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
// Check if SOH% between the packs is too large
if ((datalayer.battery.status.soh_pptt != 9900) && (datalayer.battery2.status.soh_pptt != 9900)) {
// Both values available, check diff
@ -191,10 +230,34 @@ void update_machineryprotection() {
}
#endif // DOUBLE_BATTERY
//Safeties verified, Zero charge/discharge ampere values incase any safety wrote the W to 0
if (datalayer.battery.status.max_discharge_power_W == 0) {
datalayer.battery.status.max_discharge_current_dA = 0;
}
if (datalayer.battery.status.max_charge_power_W == 0) {
datalayer.battery.status.max_charge_current_dA = 0;
}
}
//battery pause status begin
void setBatteryPause(bool pause_battery, bool pause_CAN) {
void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop, bool store_settings) {
// First handle equipment stop / resume
if (equipment_stop && !datalayer.system.settings.equipment_stop_active) {
datalayer.system.settings.equipment_stop_active = true;
if (store_settings) {
store_settings_equipment_stop();
}
set_event(EVENT_EQUIPMENT_STOP, 1);
} else if (!equipment_stop && datalayer.system.settings.equipment_stop_active) {
datalayer.system.settings.equipment_stop_active = false;
if (store_settings) {
store_settings_equipment_stop();
}
clear_event(EVENT_EQUIPMENT_STOP);
}
emulator_pause_CAN_send_ON = pause_CAN;
@ -218,14 +281,19 @@ void setBatteryPause(bool pause_battery, bool pause_CAN) {
emulator_pause_status = RESUMING;
clear_event(EVENT_PAUSE_END);
}
//immediate check if we can send CAN messages
emulator_pause_state_send_CAN_battery();
}
/// @brief handle emulator pause status
/// @return true if CAN messages should be sent to battery, false if not
void emulator_pause_state_send_CAN_battery() {
bool previous_allowed_to_send_CAN = allowed_to_send_CAN;
if (emulator_pause_status == NORMAL)
can_send_CAN = true;
if (emulator_pause_status == NORMAL) {
allowed_to_send_CAN = true;
}
// in some inverters this values are not accurate, so we need to check if we are consider 1.8 amps as the limit
if (emulator_pause_request_ON && emulator_pause_status == PAUSING && datalayer.battery.status.current_dA < 18 &&
@ -235,10 +303,18 @@ void emulator_pause_state_send_CAN_battery() {
if (!emulator_pause_request_ON && emulator_pause_status == RESUMING) {
emulator_pause_status = NORMAL;
can_send_CAN = true;
allowed_to_send_CAN = true;
}
can_send_CAN = (!emulator_pause_CAN_send_ON || emulator_pause_status == NORMAL);
allowed_to_send_CAN = (!emulator_pause_CAN_send_ON || emulator_pause_status == NORMAL);
if (previous_allowed_to_send_CAN && !allowed_to_send_CAN) {
//completely force stop the CAN communication
ESP32Can.CANStop();
} else if (!previous_allowed_to_send_CAN && allowed_to_send_CAN) {
//resume CAN communication
ESP32Can.CANInit();
}
}
std::string get_emulator_pause_status() {

View file

@ -2,23 +2,26 @@
#define SAFETY_H
#include <Arduino.h>
#include <string>
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define MAX_CAN_FAILURES 50
#define MAX_CHARGE_DISCHARGE_LIMIT_FAILURES 1
#define MAX_CHARGE_DISCHARGE_LIMIT_FAILURES 5
//battery pause status begin
enum battery_pause_status { NORMAL = 0, PAUSING = 1, PAUSED = 2, RESUMING = 3 };
extern bool emulator_pause_request_ON;
extern bool emulator_pause_CAN_send_ON;
extern battery_pause_status emulator_pause_status;
extern bool can_send_CAN;
extern bool allowed_to_send_CAN;
//battery pause status end
extern void store_settings_equipment_stop();
void update_machineryprotection();
//battery pause status begin
void setBatteryPause(bool pause_battery, bool pause_CAN);
void setBatteryPause(bool pause_battery, bool pause_CAN, bool equipment_stop = false, bool store_settings = true);
void emulator_pause_state_send_CAN_battery();
std::string get_emulator_pause_status();
//battery pause status end

View file

@ -0,0 +1,56 @@
#include "debounce_button.h"
// Function to initialize the debounced button with pin, switch type, and debounce delay
void initDebouncedButton(DebouncedButton& button, int pin, SwitchType type, unsigned long debounceDelay) {
button.pin = pin;
button.debounceDelay = debounceDelay;
button.lastDebounceTime = 0;
button.lastButtonState = (type == NC) ? HIGH : LOW; // NC starts HIGH, NO starts LOW
button.buttonState = button.lastButtonState;
button.switchType = type;
pinMode(pin, INPUT); // Setup pin mode
}
ButtonState debounceButton(DebouncedButton& button, unsigned long& timeSincePress) {
int reading = digitalRead(button.pin);
// If the button state has changed due to noise or a press
if (reading != button.lastButtonState) {
// Reset debounce timer
button.lastDebounceTime = millis();
}
// Check if the state change is stable for the debounce delay
if ((millis() - button.lastDebounceTime) > button.debounceDelay) {
if (reading != button.buttonState) {
button.buttonState = reading;
// Adjust button logic based on switch type (NC or NO)
if (button.switchType == NC) {
if (button.buttonState == LOW) {
// Button pressed for NC, record the press time
button.ulPressTime = millis();
return PRESSED;
} else {
// Button released for NC, calculate the time since last press
timeSincePress = millis() - button.ulPressTime;
return RELEASED;
}
} else { // NO type
if (button.buttonState == HIGH) {
// Button pressed for NO, record the press time
button.ulPressTime = millis();
return PRESSED;
} else {
// Button released for NO, calculate the time since last press
timeSincePress = millis() - button.ulPressTime;
return RELEASED;
}
}
}
}
// Remember the last button state
button.lastButtonState = reading;
return NONE; // No change in button state
}

View file

@ -0,0 +1,36 @@
#ifndef DEBOUNCE_H
#define DEBOUNCE_H
#include <Arduino.h>
// Enum to define switch type (Normally Closed - NC, Normally Open - NO)
enum SwitchType {
NC, // Normally Closed
NO // Normally Open
};
// Enum to define button state
enum ButtonState {
NONE, // No change in button state
PRESSED, // Button is pressed down
RELEASED // Button is released
};
// Struct to hold button state and debounce parameters
struct DebouncedButton {
int pin; // GPIO pin number
unsigned long lastDebounceTime; // Time of last state change
unsigned long debounceDelay; // Debounce delay time
unsigned long ulPressTime; // Time when the button was last pressed
bool lastButtonState; // Previous button state
bool buttonState; // Current button state
SwitchType switchType; // Switch type (NC or NO)
};
// Function to initialize the debounced button
void initDebouncedButton(DebouncedButton& button, int pin, SwitchType type, unsigned long debounceDelay = 50);
// Function to debounce button and return the button state (PRESSED, RELEASED, or NONE)
ButtonState debounceButton(DebouncedButton& button, unsigned long& timeSincePress);
#endif // DEBOUNCE_H

View file

@ -44,16 +44,14 @@
typedef struct {
EVENTS_ENUM_TYPE event;
uint8_t millisrolloverCount;
uint32_t timestamp;
uint8_t data;
} EVENT_LOG_ENTRY_TYPE;
typedef struct {
EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
unsigned long time_seconds;
MyTimer second_timer;
MyTimer ee_timer;
MyTimer update_timer;
EVENTS_LEVEL_TYPE level;
uint16_t event_log_head_index;
uint16_t event_log_tail_index;
@ -66,21 +64,29 @@ static EVENT_TYPE events;
static const char* EVENTS_ENUM_TYPE_STRING[] = {EVENTS_ENUM_TYPE(GENERATE_STRING)};
static const char* EVENTS_LEVEL_TYPE_STRING[] = {EVENTS_LEVEL_TYPE(GENERATE_STRING)};
static uint32_t lastMillis = millis();
/* Local function prototypes */
static void update_event_time(void);
static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched);
static void update_event_level(void);
static void update_bms_status(void);
static void log_event(EVENTS_ENUM_TYPE event, uint8_t data);
static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint32_t timestamp, uint8_t data);
static void print_event_log(void);
static void check_ee_write(void);
uint8_t millisrolloverCount = 0;
/* Exported functions */
/* Main execution function, should handle various continuous functionality */
void run_event_handling(void) {
update_event_time();
uint32_t currentMillis = millis();
if (currentMillis < lastMillis) { // Overflow detected
millisrolloverCount++;
}
lastMillis = currentMillis;
run_sequence_on_target();
//check_ee_write();
update_event_level();
@ -100,7 +106,7 @@ void init_events(void) {
EEPROM.writeUShort(EE_EVENT_LOG_TAIL_INDEX_ADDRESS, 0);
// Prepare an empty event block to write
EVENT_LOG_ENTRY_TYPE entry = {.event = EVENT_NOF_EVENTS, .timestamp = 0, .data = 0};
EVENT_LOG_ENTRY_TYPE entry = {.event = EVENT_NOF_EVENTS, .millisrolloverCount = 0, .timestamp = 0, .data = 0};
// Put the event in (what I guess is) the RAM EEPROM mirror, or write buffer
@ -112,15 +118,15 @@ void init_events(void) {
// Push changes to eeprom
EEPROM.commit();
#ifdef DEBUG_VIA_USB
Serial.println("EEPROM wasn't ready");
#ifdef DEBUG_LOG
logging.println("EEPROM wasn't ready");
#endif
} else {
events.event_log_head_index = EEPROM.readUShort(EE_EVENT_LOG_HEAD_INDEX_ADDRESS);
events.event_log_tail_index = EEPROM.readUShort(EE_EVENT_LOG_TAIL_INDEX_ADDRESS);
#ifdef DEBUG_VIA_USB
Serial.println("EEPROM was initialized for event logging");
Serial.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index));
#ifdef DEBUG_LOG
logging.println("EEPROM was initialized for event logging");
logging.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index));
#endif
print_event_log();
}
@ -128,23 +134,29 @@ void init_events(void) {
for (uint16_t i = 0; i < EVENT_NOF_EVENTS; i++) {
events.entries[i].data = 0;
events.entries[i].timestamp = 0;
events.entries[i].millisrolloverCount = 0;
events.entries[i].occurences = 0;
events.entries[i].log = true;
events.entries[i].MQTTpublished = false; // Not published by default
}
events.entries[EVENT_CANFD_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANMCP2517FD_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANMCP2515_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CANFD_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CANFD_RX_OVERRUN].level = EVENT_LEVEL_WARNING;
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_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
events.entries[EVENT_12V_LOW].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SOC_UNAVAILABLE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_KWH_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO;
@ -157,6 +169,7 @@ void init_events(void) {
events.entries[EVENT_BATTERY_OVERHEAT].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_BATTERY_OVERVOLTAGE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_BATTERY_UNDERVOLTAGE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_BATTERY_VALUE_UNAVAILABLE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_BATTERY_ISOLATION].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_VOLTAGE_DIFFERENCE].level = EVENT_LEVEL_INFO;
events.entries[EVENT_SOH_DIFFERENCE].level = EVENT_LEVEL_WARNING;
@ -178,6 +191,7 @@ void init_events(void) {
events.entries[EVENT_DUMMY_DEBUG].level = EVENT_LEVEL_DEBUG;
events.entries[EVENT_DUMMY_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_DUMMY_ERROR].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_PERSISTENT_SAVE_INFO].level = EVENT_LEVEL_INFO;
events.entries[EVENT_SERIAL_RX_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SERIAL_RX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_SERIAL_TX_FAILURE].level = EVENT_LEVEL_ERROR;
@ -201,13 +215,16 @@ void init_events(void) {
events.entries[EVENT_RESET_CPU_LOCKUP].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_PAUSE_BEGIN].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_PAUSE_END].level = EVENT_LEVEL_INFO;
events.entries[EVENT_WIFI_CONNECT].level = EVENT_LEVEL_INFO;
events.entries[EVENT_WIFI_DISCONNECT].level = EVENT_LEVEL_INFO;
events.entries[EVENT_MQTT_CONNECT].level = EVENT_LEVEL_INFO;
events.entries[EVENT_MQTT_DISCONNECT].level = EVENT_LEVEL_INFO;
events.entries[EVENT_EQUIPMENT_STOP].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_EEPROM_WRITE].log = false; // Don't log the logger...
events.second_timer.set_interval(600);
// Write to EEPROM every X minutes (if an event has been set)
events.ee_timer.set_interval(EE_WRITE_PERIOD_MINUTES * 60 * 1000);
events.update_timer.set_interval(2000);
}
void set_event(EVENTS_ENUM_TYPE event, uint8_t data) {
@ -226,16 +243,37 @@ void clear_event(EVENTS_ENUM_TYPE event) {
}
}
void reset_all_events() {
events.nof_logged_events = 0;
for (uint16_t i = 0; i < EVENT_NOF_EVENTS; i++) {
events.entries[i].data = 0;
events.entries[i].state = EVENT_STATE_INACTIVE;
events.entries[i].timestamp = 0;
events.entries[i].millisrolloverCount = 0;
events.entries[i].occurences = 0;
events.entries[i].log = true;
events.entries[i].MQTTpublished = false; // Not published by default
}
events.level = EVENT_LEVEL_INFO;
update_bms_status();
}
void set_event_MQTTpublished(EVENTS_ENUM_TYPE event) {
events.entries[event].MQTTpublished = true;
}
const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
switch (event) {
case EVENT_CANFD_INIT_FAILURE:
case EVENT_CANMCP2517FD_INIT_FAILURE:
return "CAN-FD initialization failed. Check hardware or bitrate settings";
case EVENT_CANMCP2515_INIT_FAILURE:
return "CAN-MCP addon initialization failed. Check hardware";
case EVENT_CANFD_BUFFER_FULL:
return "CAN-FD buffer overflowed. Some CAN messages were not sent. Contact developers.";
case EVENT_CAN_OVERRUN:
return "CAN message failed to send within defined time. Contact developers, CPU load might be too high.";
case EVENT_CANFD_RX_OVERRUN:
return "CAN-FD failed to receive all messages from CAN bus. Contact developers, CPU load might be too high.";
case EVENT_CAN_RX_FAILURE:
return "No CAN communication detected for 60s. Shutting down battery control.";
case EVENT_CAN2_RX_FAILURE:
@ -246,6 +284,10 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!";
case EVENT_CAN_TX_FAILURE:
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
case EVENT_CAN_INVERTER_MISSING:
return "Warning: Inverter not sending messages on CAN bus. Check wiring!";
case EVENT_CONTACTOR_WELDED:
return "Warning: Contactors sticking/welded. Inspect battery with caution!";
case EVENT_CHARGE_LIMIT_EXCEEDED:
return "Info: Inverter is charging faster than battery is allowing.";
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
@ -255,9 +297,9 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_12V_LOW:
return "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!";
case EVENT_SOC_PLAUSIBILITY_ERROR:
return "ERROR: SOC% reported by battery not plausible. Restart battery!";
return "Warning: SOC reported by battery not plausible. Restart battery!";
case EVENT_SOC_UNAVAILABLE:
return "Warning: SOC% not sent by BMS. Calibrate BMS via app.";
return "Warning: SOC not sent by BMS. Calibrate BMS via app.";
case EVENT_KWH_PLAUSIBILITY_ERROR:
return "Info: kWh remaining reported by battery not plausible. Battery needs cycling.";
case EVENT_BATTERY_EMPTY:
@ -284,6 +326,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "Warning: Battery exceeding maximum design voltage. Discharge battery to prevent damage!";
case EVENT_BATTERY_UNDERVOLTAGE:
return "Warning: Battery under minimum design voltage. Charge battery to prevent damage!";
case EVENT_BATTERY_VALUE_UNAVAILABLE:
return "Warning: Battery measurement unavailable. Check 12V power supply and battery wiring!";
case EVENT_BATTERY_ISOLATION:
return "Warning: Battery reports isolation error. High voltage might be leaking to ground. Check battery!";
case EVENT_VOLTAGE_DIFFERENCE:
@ -325,6 +369,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "The dummy warning event was set!"; // Don't change this event message!
case EVENT_DUMMY_ERROR:
return "The dummy error event was set!"; // Don't change this event message!
case EVENT_PERSISTENT_SAVE_INFO:
return "Info: Failed to save user settings. Namespace full?";
case EVENT_SERIAL_RX_WARNING:
return "Error in serial function: No data received for some time, see data for minutes";
case EVENT_SERIAL_RX_FAILURE:
@ -376,6 +422,16 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
return "Warning: The emulator is trying to pause the battery.";
case EVENT_PAUSE_END:
return "Info: The emulator is attempting to resume battery operation from pause.";
case EVENT_WIFI_CONNECT:
return "Info: Wifi connected.";
case EVENT_WIFI_DISCONNECT:
return "Info: Wifi disconnected.";
case EVENT_MQTT_CONNECT:
return "Info: MQTT connected.";
case EVENT_MQTT_DISCONNECT:
return "Info: MQTT disconnected.";
case EVENT_EQUIPMENT_STOP:
return "ERROR: EQUIPMENT STOP ACTIVATED!!!";
default:
return "";
}
@ -413,12 +469,17 @@ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) {
events.entries[event].occurences++;
events.entries[event].MQTTpublished = false;
if (events.entries[event].log) {
log_event(event, data);
log_event(event, events.entries[event].millisrolloverCount, events.entries[event].timestamp, data);
}
#ifdef DEBUG_LOG
logging.print("Event: ");
logging.println(get_event_message_string(event));
#endif
}
// We should set the event, update event info
events.entries[event].timestamp = events.time_seconds;
events.entries[event].timestamp = millis();
events.entries[event].millisrolloverCount = millisrolloverCount;
events.entries[event].data = data;
// Check if the event is latching
events.entries[event].state = latched ? EVENT_STATE_ACTIVE_LATCHED : EVENT_STATE_ACTIVE;
@ -427,10 +488,6 @@ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) {
events.level = max(events.level, events.entries[event].level);
update_bms_status();
#ifdef DEBUG_VIA_USB
Serial.println(get_event_message_string(event));
#endif
}
static void update_bms_status(void) {
@ -451,6 +508,22 @@ static void update_bms_status(void) {
}
}
// Function to compare events by timestamp descending
bool compareEventsByTimestampDesc(const EventData& a, const EventData& b) {
if (a.event_pointer->millisrolloverCount != b.event_pointer->millisrolloverCount) {
return a.event_pointer->millisrolloverCount > b.event_pointer->millisrolloverCount;
}
return a.event_pointer->timestamp > b.event_pointer->timestamp;
}
// Function to compare events by timestamp ascending
bool compareEventsByTimestampAsc(const EventData& a, const EventData& b) {
if (a.event_pointer->millisrolloverCount != b.event_pointer->millisrolloverCount) {
return a.event_pointer->millisrolloverCount < b.event_pointer->millisrolloverCount;
}
return a.event_pointer->timestamp < b.event_pointer->timestamp;
}
static void update_event_level(void) {
EVENTS_LEVEL_TYPE temporary_level = EVENT_LEVEL_INFO;
for (uint8_t i = 0u; i < EVENT_NOF_EVENTS; i++) {
@ -461,22 +534,7 @@ static void update_event_level(void) {
events.level = temporary_level;
}
static void update_event_time(void) {
// This should run roughly 2 times per second
if (events.second_timer.elapsed() == true) {
uptime::calculateUptime(); // millis() overflows every 50 days, so update occasionally to adjust
events.time_seconds = uptime::getDays() * DAYS_TO_SECS;
events.time_seconds += uptime::getHours() * HOURS_TO_SECS;
events.time_seconds += uptime::getMinutes() * MINUTES_TO_SECS;
events.time_seconds += uptime::getSeconds();
}
}
unsigned long get_current_event_time_secs(void) {
return events.time_seconds;
}
static void log_event(EVENTS_ENUM_TYPE event, uint8_t data) {
static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint32_t timestamp, uint8_t data) {
// Update head with wrap to 0
if (++events.event_log_head_index == EE_NOF_EVENT_ENTRIES) {
events.event_log_head_index = 0;
@ -494,7 +552,8 @@ static void log_event(EVENTS_ENUM_TYPE event, uint8_t data) {
int entry_address = EE_EVENT_ENTRY_START_ADDRESS + EE_EVENT_ENTRY_SIZE * events.event_log_head_index;
// Prepare an event block to write
EVENT_LOG_ENTRY_TYPE entry = {.event = event, .timestamp = events.time_seconds, .data = data};
EVENT_LOG_ENTRY_TYPE entry = {
.event = event, .millisrolloverCount = millisrolloverCount, .timestamp = timestamp, .data = data};
// Put the event in (what I guess is) the RAM EEPROM mirror, or write buffer
EEPROM.put(entry_address, entry);
@ -502,8 +561,8 @@ static void log_event(EVENTS_ENUM_TYPE event, uint8_t data) {
// Store the new indices
EEPROM.writeUShort(EE_EVENT_LOG_HEAD_INDEX_ADDRESS, events.event_log_head_index);
EEPROM.writeUShort(EE_EVENT_LOG_TAIL_INDEX_ADDRESS, events.event_log_tail_index);
//Serial.println("Wrote event " + String(event) + " to " + String(entry_address));
//Serial.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index));
//logging.println("Wrote event " + String(event) + " to " + String(entry_address));
//logging.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index));
// We don't need the exact number, it's just for deciding to store or not
events.nof_logged_events += (events.nof_logged_events < 255) ? 1 : 0;
@ -512,8 +571,8 @@ static void log_event(EVENTS_ENUM_TYPE event, uint8_t data) {
static void print_event_log(void) {
// If the head actually points to the tail, the log is probably blank
if (events.event_log_head_index == events.event_log_tail_index) {
#ifdef DEBUG_VIA_USB
Serial.println("No events in log");
#ifdef DEBUG_LOG
logging.println("No events in log");
#endif
return;
}
@ -529,9 +588,9 @@ static void print_event_log(void) {
// The entry is a blank that has been left behind somehow
continue;
}
#ifdef DEBUG_VIA_USB
Serial.println("Event: " + String(get_event_enum_string(entry.event)) + ", data: " + String(entry.data) +
", time: " + String(entry.timestamp));
#ifdef DEBUG_LOG
logging.println("Event: " + String(get_event_enum_string(entry.event)) + ", data: " + String(entry.data) +
", time: " + String(entry.timestamp));
#endif
if (index == events.event_log_head_index) {
break;

View file

@ -6,7 +6,7 @@
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
#define EE_MAGIC_HEADER_VALUE 0x0012 // 0x0000 to 0xFFFF
#define EE_MAGIC_HEADER_VALUE 0x0018 // 0x0000 to 0xFFFF
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
@ -26,14 +26,19 @@
*/
#define EVENTS_ENUM_TYPE(XX) \
XX(EVENT_CANFD_INIT_FAILURE) \
XX(EVENT_CANMCP2517FD_INIT_FAILURE) \
XX(EVENT_CANMCP2515_INIT_FAILURE) \
XX(EVENT_CANFD_BUFFER_FULL) \
XX(EVENT_CAN_OVERRUN) \
XX(EVENT_CANFD_RX_OVERRUN) \
XX(EVENT_CAN_RX_FAILURE) \
XX(EVENT_CAN2_RX_FAILURE) \
XX(EVENT_CANFD_RX_FAILURE) \
XX(EVENT_CAN_RX_WARNING) \
XX(EVENT_CAN_TX_FAILURE) \
XX(EVENT_CAN_INVERTER_MISSING) \
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
XX(EVENT_CONTACTOR_WELDED) \
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
XX(EVENT_WATER_INGRESS) \
XX(EVENT_12V_LOW) \
@ -50,6 +55,7 @@
XX(EVENT_BATTERY_OVERHEAT) \
XX(EVENT_BATTERY_OVERVOLTAGE) \
XX(EVENT_BATTERY_UNDERVOLTAGE) \
XX(EVENT_BATTERY_VALUE_UNAVAILABLE) \
XX(EVENT_BATTERY_ISOLATION) \
XX(EVENT_BATTERY_REQUESTS_HEAT) \
XX(EVENT_BATTERY_WARMED_UP) \
@ -73,6 +79,7 @@
XX(EVENT_DUMMY_DEBUG) \
XX(EVENT_DUMMY_WARNING) \
XX(EVENT_DUMMY_ERROR) \
XX(EVENT_PERSISTENT_SAVE_INFO) \
XX(EVENT_SERIAL_RX_WARNING) \
XX(EVENT_SERIAL_RX_FAILURE) \
XX(EVENT_SERIAL_TX_FAILURE) \
@ -96,6 +103,11 @@
XX(EVENT_RESET_CPU_LOCKUP) \
XX(EVENT_PAUSE_BEGIN) \
XX(EVENT_PAUSE_END) \
XX(EVENT_WIFI_CONNECT) \
XX(EVENT_WIFI_DISCONNECT) \
XX(EVENT_MQTT_CONNECT) \
XX(EVENT_MQTT_DISCONNECT) \
XX(EVENT_EQUIPMENT_STOP) \
XX(EVENT_NOF_EVENTS)
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;
@ -118,20 +130,28 @@ typedef enum {
} EVENTS_STATE_TYPE;
typedef struct {
uint32_t timestamp; // Time in seconds since startup when the event occurred
uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage
uint8_t occurences; // Number of occurrences since startup
EVENTS_LEVEL_TYPE level; // Event level, i.e. ERROR/WARNING...
EVENTS_STATE_TYPE state; // Event state, i.e. ACTIVE/INACTIVE...
uint32_t timestamp; // Time in seconds since startup when the event occurred
uint8_t millisrolloverCount; // number of times millis rollovers before timestamp
uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage
uint8_t occurences; // Number of occurrences since startup
EVENTS_LEVEL_TYPE level; // Event level, i.e. ERROR/WARNING...
EVENTS_STATE_TYPE state; // Event state, i.e. ACTIVE/INACTIVE...
bool log;
bool MQTTpublished;
} EVENTS_STRUCT_TYPE;
// Define a struct to hold event data
struct EventData {
EVENTS_ENUM_TYPE event_handle;
const EVENTS_STRUCT_TYPE* event_pointer;
};
extern uint8_t millisrolloverCount; // number of times millis rollovers
const char* get_event_enum_string(EVENTS_ENUM_TYPE event);
const char* get_event_message_string(EVENTS_ENUM_TYPE event);
const char* get_event_level_string(EVENTS_ENUM_TYPE event);
const char* get_event_type(EVENTS_ENUM_TYPE event);
unsigned long get_current_event_time_secs(void);
EVENTS_LEVEL_TYPE get_event_level(void);
@ -139,6 +159,7 @@ void init_events(void);
void set_event_latched(EVENTS_ENUM_TYPE event, uint8_t data);
void set_event(EVENTS_ENUM_TYPE event, uint8_t data);
void clear_event(EVENTS_ENUM_TYPE event);
void reset_all_events();
void set_event_MQTTpublished(EVENTS_ENUM_TYPE event);
const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event);
@ -147,4 +168,7 @@ void run_event_handling(void);
void run_sequence_on_target(void);
bool compareEventsByTimestampAsc(const EventData& a, const EventData& b);
bool compareEventsByTimestampDesc(const EventData& a, const EventData& b);
#endif // __MYTIMER_H__

View file

@ -25,9 +25,9 @@ void run_sequence_on_target(void) {
case ETOT_INIT:
timer.set_interval(10000);
events_test_state = ETOT_FIRST_WAIT;
Serial.println("Events test: initialized");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test: initialized");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
break;
case ETOT_FIRST_WAIT:
if (timer.elapsed()) {
@ -35,9 +35,9 @@ void run_sequence_on_target(void) {
events_test_state = ETOT_INFO;
set_event(EVENT_DUMMY_INFO, 123);
set_event(EVENT_DUMMY_INFO, 234); // 234 should show, occurrence 1
Serial.println("Events test: info event set, data: 234");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test: info event set, data: 234");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_INFO:
@ -45,9 +45,9 @@ void run_sequence_on_target(void) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_INFO);
events_test_state = ETOT_INFO_CLEAR;
Serial.println("Events test : info event cleared");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : info event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_INFO_CLEAR:
@ -56,9 +56,9 @@ void run_sequence_on_target(void) {
events_test_state = ETOT_DEBUG;
set_event(EVENT_DUMMY_DEBUG, 111);
set_event(EVENT_DUMMY_DEBUG, 222); // 222 should show, occurrence 1
Serial.println("Events test : debug event set, data: 222");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : debug event set, data: 222");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_DEBUG:
@ -66,9 +66,9 @@ void run_sequence_on_target(void) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_DEBUG);
events_test_state = ETOT_DEBUG_CLEAR;
Serial.println("Events test : info event cleared");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : info event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_DEBUG_CLEAR:
@ -77,9 +77,9 @@ void run_sequence_on_target(void) {
events_test_state = ETOT_WARNING;
set_event(EVENT_DUMMY_WARNING, 234);
set_event(EVENT_DUMMY_WARNING, 121); // 121 should show, occurrence 1
Serial.println("Events test : warning event set, data: 121");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : warning event set, data: 121");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_WARNING:
@ -87,9 +87,9 @@ void run_sequence_on_target(void) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_WARNING);
events_test_state = ETOT_WARNING_CLEAR;
Serial.println("Events test : warning event cleared");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : warning event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_WARNING_CLEAR:
@ -98,9 +98,9 @@ void run_sequence_on_target(void) {
events_test_state = ETOT_ERROR;
set_event(EVENT_DUMMY_ERROR, 221);
set_event(EVENT_DUMMY_ERROR, 133); // 133 should show, occurrence 1
Serial.println("Events test : error event set, data: 133");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : error event set, data: 133");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_ERROR:
@ -108,9 +108,9 @@ void run_sequence_on_target(void) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_ERROR);
events_test_state = ETOT_ERROR_CLEAR;
Serial.println("Events test : error event cleared");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : error event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_ERROR_CLEAR:
@ -119,9 +119,9 @@ void run_sequence_on_target(void) {
events_test_state = ETOT_ERROR_LATCHED;
set_event_latched(EVENT_DUMMY_ERROR, 221);
set_event_latched(EVENT_DUMMY_ERROR, 133); // 133 should show, occurrence 1
Serial.println("Events test : latched error event set, data: 133");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : latched error event set, data: 133");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_ERROR_LATCHED:
@ -129,9 +129,9 @@ void run_sequence_on_target(void) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_ERROR);
events_test_state = ETOT_DONE;
Serial.println("Events test : latched error event cleared?");
Serial.print("datalayer.battery.status.bms_status: ");
Serial.println(datalayer.battery.status.bms_status);
logging.println("Events test : latched error event cleared?");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_DONE:

View file

@ -0,0 +1,86 @@
#include "logging.h"
#include "../../datalayer/datalayer.h"
size_t Logging::write(const uint8_t* buffer, size_t size) {
#ifdef DEBUG_LOG
char* message_string = datalayer.system.info.logged_can_messages;
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
unsigned long currentTime = millis();
#ifdef DEBUG_VIA_USB
size_t n = 0;
while (size--) {
if (Serial.write(*buffer++))
n++;
else
break;
}
return n;
#endif
#ifdef DEBUG_VIA_WEB
if (datalayer.system.info.can_logging_active) {
return 0;
}
if (offset + size + 13 > sizeof(datalayer.system.info.logged_can_messages)) {
offset = 0;
}
if (buffer[0] != '\r' && buffer[0] != '\n' &&
(offset == 0 || message_string[offset - 1] == '\r' || message_string[offset - 1] == '\n')) {
offset += snprintf(message_string + offset, message_string_size - offset - 1, "%8lu.%03lu ", currentTime / 1000,
currentTime % 1000);
}
memcpy(message_string + offset, buffer, size);
datalayer.system.info.logged_can_messages_offset = offset + size; // Update offset in buffer
return size;
#endif // DEBUG_VIA_WEB
#endif // DEBUG_LOG
return 0;
}
void Logging::printf(const char* fmt, ...) {
#ifdef DEBUG_LOG
char* message_string = datalayer.system.info.logged_can_messages;
int offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
size_t message_string_size = sizeof(datalayer.system.info.logged_can_messages);
#ifdef DEBUG_VIA_USB
static char buf[128];
message_string = buf;
offset = 0;
message_string_size = sizeof(buf);
#endif
#ifdef DEBUG_VIA_WEB
if (datalayer.system.info.can_logging_active) {
return;
}
message_string = datalayer.system.info.logged_can_messages;
offset = datalayer.system.info.logged_can_messages_offset; // Keeps track of the current position in the buffer
message_string_size = sizeof(datalayer.system.info.logged_can_messages);
#endif
if (offset + 128 > sizeof(datalayer.system.info.logged_can_messages)) {
// Not enough space, reset and start from the beginning
offset = 0;
}
unsigned long currentTime = millis();
// Add timestamp
offset += snprintf(message_string + offset, message_string_size - offset - 1, "%8lu.%03lu ", currentTime / 1000,
currentTime % 1000);
va_list(args);
va_start(args, fmt);
offset += vsnprintf(message_string + offset, message_string_size - offset - 1, fmt, args);
va_end(args);
if (datalayer.system.info.can_logging_active) {
size_t size = offset;
size_t n = 0;
while (size--) {
if (Serial.write(*message_string++))
n++;
else
break;
}
} else {
datalayer.system.info.logged_can_messages_offset = offset; // Update offset in buffer
}
#endif // DEBUG_LOG
}

View file

@ -0,0 +1,16 @@
#ifndef __LOGGING_H__
#define __LOGGING_H__
#include <inttypes.h>
#include "Print.h"
class Logging : public Print {
public:
virtual size_t write(const uint8_t* buffer, size_t size);
virtual size_t write(uint8_t) { return 0; }
void printf(const char* fmt, ...);
Logging() {}
};
extern Logging logging;
#endif // __LOGGING_H__

View file

@ -13,9 +13,12 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB };
#define INTERVAL_10_MS 10
#define INTERVAL_20_MS 20
#define INTERVAL_30_MS 30
#define INTERVAL_40_MS 40
#define INTERVAL_50_MS 50
#define INTERVAL_70_MS 70
#define INTERVAL_100_MS 100
#define INTERVAL_200_MS 200
#define INTERVAL_250_MS 250
#define INTERVAL_500_MS 500
#define INTERVAL_640_MS 640
#define INTERVAL_1_S 1000
@ -32,8 +35,8 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB };
#define INTERVAL_200_MS_DELAYED 240
#define INTERVAL_500_MS_DELAYED 550
#define CAN_STILL_ALIVE 12
// Set by battery each time we get a CAN message. Decrements every 5seconds. When reaching 0, sets event
#define CAN_STILL_ALIVE 60
// Set by battery each time we get a CAN message. Decrements every second. When reaching 0, sets event
/* CAN Frame structure */
typedef struct {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
#ifndef ADVANCEDBATTERY_H
#define ADVANCEDBATTERY_H
#include <Arduino.h>
#include <string>
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String advanced_battery_processor(const String& var);
#endif

View file

@ -0,0 +1,62 @@
#include "can_logging_html.h"
#include <Arduino.h>
#include "../../datalayer/datalayer.h"
String can_logger_processor(const String& var) {
if (var == "X") {
if (!datalayer.system.info.can_logging_active) {
datalayer.system.info.logged_can_messages_offset = 0;
datalayer.system.info.logged_can_messages[0] = '\0';
}
datalayer.system.info.can_logging_active =
true; // Signal to main loop that we should log messages. Disabled by default for performance reasons
String content = "";
// Page format
content += "<style>";
content += "body { background-color: black; color: white; font-family: Arial, sans-serif; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content +=
".can-message { background-color: #404E57; margin-bottom: 5px; padding: 10px; border-radius: 5px; font-family: "
"monospace; }";
content += "</style>";
content += "<button onclick='refreshPage()'>Refresh data</button> ";
content += "<button onclick='exportLog()'>Export to .txt</button> ";
content += "<button onclick='stopLoggingAndGoToMainPage()'>Back to main page</button>";
// Start a new block for the CAN messages
content += "<div style='background-color: #303E47; padding: 20px; border-radius: 15px'>";
// Check for messages
if (datalayer.system.info.logged_can_messages[0] == 0) {
content += "CAN logger started! Refresh page to display incoming(RX) and outgoing(TX) messages";
} else {
// Split the messages using the newline character
String messages = String(datalayer.system.info.logged_can_messages);
int startIndex = 0;
int endIndex = messages.indexOf('\n');
while (endIndex != -1) {
// Extract a single message and wrap it in a styled div
String singleMessage = messages.substring(startIndex, endIndex);
content += "<div class='can-message'>" + singleMessage + "</div>";
startIndex = endIndex + 1; // Move past the newline character
endIndex = messages.indexOf('\n', startIndex);
}
}
content += "</div>";
// Add JavaScript for navigation
content += "<script>";
content += "function refreshPage(){ location.reload(true); }";
content += "function exportLog() { window.location.href = '/export_can_log'; }";
content += "function stopLoggingAndGoToMainPage() {";
content += " fetch('/stop_can_logging').then(() => window.location.href = '/');";
content += "}";
content += "</script>";
return content;
}
return String();
}

View file

@ -0,0 +1,16 @@
#ifndef CANLOGGER_H
#define CANLOGGER_H
#include <Arduino.h>
#include <string>
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String can_logger_processor(const String& var);
#endif

View file

@ -8,6 +8,10 @@ String cellmonitor_processor(const String& var) {
// Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content += ".container { display: flex; flex-wrap: wrap; justify-content: space-around; }";
content += ".cell { width: 48%; margin: 1%; padding: 10px; border: 1px solid white; text-align: center; }";
content += ".low-voltage { color: red; }"; // Style for low voltage text

View file

@ -0,0 +1,36 @@
#include "debug_logging_html.h"
#include <Arduino.h>
#include "../../datalayer/datalayer.h"
#ifdef DEBUG_VIA_WEB
String debug_logger_processor(const String& var) {
String content = "";
// Page format
content += "<style>";
content += "body { background-color: black; color: white; font-family: Arial, sans-serif; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content +=
".can-message { background-color: #404E57; margin-bottom: 5px; padding: 10px; border-radius: 5px; font-family: "
"monospace; }";
content += "</style>";
content += "<button onclick='refreshPage()'>Refresh data</button> ";
content += "<button onclick='exportLog()'>Export to .txt</button> ";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
// Start a new block for the debug log messages
content += "<PRE style='text-align: left'>";
content += String(datalayer.system.info.logged_can_messages);
content += "</PRE>";
// Add JavaScript for navigation
content += "<script>";
content += "function refreshPage(){ location.reload(true); }";
content += "function exportLog() { window.location.href = '/export_log'; }";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
return content;
}
#endif // DEBUG_VIA_WEB

View file

@ -0,0 +1,16 @@
#ifndef DEBUGLOGGER_H
#define DEBUGLOGGER_H
#include <Arduino.h>
#include <string>
/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String debug_logger_processor(const String& var);
#endif

View file

@ -5,18 +5,15 @@ const char EVENTS_HTML_START[] = R"=====(
)=====";
const char EVENTS_HTML_END[] = R"=====(
</div></div>
<button onclick='home()'>Back to main page</button>
<style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style>
<script>function showEvent(){document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago");n&&(n.innerText=new Date(new Date().getTime()-1e3*parseInt(n.innerText,10)).toLocaleString())})}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script>
<style> button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; cursor: pointer; border-radius: 10px; }
button:hover { background-color: #3A4A52; }</style>
<button onclick="askClear()">Clear all events</button>
<button onclick="home()">Back to main page</button>
<style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style><script>function showEvent(){document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago");n&&(n.innerText=new Date(Date.now()-(4294967296*+n.innerText.split(";")[0]+ +n.innerText.split(";")[1])).toLocaleString())})}function askClear(){window.confirm("Are you sure you want to clear all events?")&&(window.location.href="/clearevents")}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script>
)=====";
static std::vector<EventData> order_events;
// Function to compare events by timestamp
static bool compareEventsByTimestamp(const EventData& a, const EventData& b) {
return a.event_pointer->timestamp > b.event_pointer->timestamp;
}
String events_processor(const String& var) {
if (var == "X") {
String content = "";
@ -25,8 +22,6 @@ String events_processor(const String& var) {
content.concat(FPSTR(EVENTS_HTML_START));
const EVENTS_STRUCT_TYPE* event_pointer;
unsigned long timestamp_now = get_current_event_time_secs();
//clear the vector
order_events.clear();
// Collect all events
@ -36,24 +31,25 @@ String events_processor(const String& var) {
order_events.push_back({static_cast<EVENTS_ENUM_TYPE>(i), event_pointer});
}
}
// Sort events by timestamp
std::sort(order_events.begin(), order_events.end(), compareEventsByTimestamp);
std::sort(order_events.begin(), order_events.end(), compareEventsByTimestampDesc);
unsigned long timestamp_now = millis();
// Generate HTML and debug output
for (const auto& event : order_events) {
EVENTS_ENUM_TYPE event_handle = event.event_handle;
event_pointer = event.event_pointer;
#ifdef DEBUG_VIA_USB
Serial.println("Event: " + String(get_event_enum_string(event_handle)) +
" count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) +
" data: " + String(event_pointer->data) +
" level: " + String(get_event_level_string(event_handle)));
#ifdef DEBUG_LOG
logging.println("Showing Event: " + String(get_event_enum_string(event_handle)) +
" count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) +
" data: " + String(event_pointer->data) +
" level: " + String(get_event_level_string(event_handle)));
#endif
content.concat("<div class='event'>");
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>");
content.concat("<div class='sec-ago'>" + String(timestamp_now - event_pointer->timestamp) + "</div>");
content.concat("<div class='sec-ago'>" + String(millisrolloverCount) + ";" +
String(timestamp_now - event_pointer->timestamp) + "</div>");
content.concat("<div>" + String(event_pointer->occurences) + "</div>");
content.concat("<div>" + String(event_pointer->data) + "</div>");
content.concat("<div>" + String(get_event_message_string(event_handle)) + "</div>");
@ -64,41 +60,38 @@ String events_processor(const String& var) {
order_events.clear();
content.concat(FPSTR(EVENTS_HTML_END));
return content;
return String();
}
return String();
}
/* Script for displaying event log before it gets minified
<button onclick="askClear()">Clear all events</button>
<button onclick="home()">Back to main page</button>
<style>
.event:nth-child(even) {
background-color: #455a64;
}
.event:nth-child(odd) {
background-color: #394b52;
}
</style>
<script>
function showEvent() {
var eventLogElement = document.querySelector('.event-log');
// Get the current time on the client side
var currentTime = new Date().getTime() / 1000; // Convert milliseconds to seconds
// Loop through the events and update the "Last Event" column
var events = document.querySelectorAll('.event');
events.forEach(function(event) {
var secondsAgoElement = event.querySelector('.sec-ago');
var timestampElement = event.querySelector('.timestamp');
if (secondsAgoElement && timestampElement) {
var secondsAgo = parseInt(secondsAgoElement.innerText, 10);
var uptimeTimestamp = parseFloat(timestampElement.innerText); // Parse as float to handle seconds with decimal parts
// Calculate the actual system time based on the client-side current time
var actualTime = new Date((currentTime - uptimeTimestamp + secondsAgo) * 1000);
// Format the date and time
var formattedTime = actualTime.toLocaleString();
// Update the "Last Event" column with the formatted time
secondsAgoElement.innerText = formattedTime;
}
});
}
// Call the showEvent function when the page is loaded
window.onload = function() {
showEvent();
};
function home() {
window.location.href = '/';
}
function showEvent() {
document.querySelectorAll(".event").forEach(function (e) {
var n = e.querySelector(".sec-ago");
n && (n.innerText = new Date(Date.now() - (+n.innerText.split(";")[0] * 4294967296 + +n.innerText.split(";")[1])).toLocaleString());
});
}
function askClear() {
if (window.confirm('Are you sure you want to clear all events?')) {
window.location.href = '/clearevents';
}
}
function home() {
window.location.href = "/";
}
window.onload = function () {
showEvent();
};
</script>
*/

View file

@ -14,10 +14,5 @@
* @return String
*/
String events_processor(const String& var);
// Define a struct to hold event data
struct EventData {
EVENTS_ENUM_TYPE event_handle;
const EVENTS_STRUCT_TYPE* event_pointer;
};
#endif

View file

@ -8,8 +8,14 @@ String settings_processor(const String& var) {
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content += "</style>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
// Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
@ -55,12 +61,27 @@ String settings_processor(const String& var) {
content += "<h4 style='color: " + String(datalayer.battery.settings.soc_scaling_active ? "white" : "darkgrey") +
";'>SOC min percentage: " + String(datalayer.battery.settings.min_percentage / 100.0, 1) +
" </span> <button onclick='editSocMin()'>Edit</button></h4>";
content +=
"<h4 style='color: white;'>Max charge speed: " + String(datalayer.battery.info.max_charge_amp_dA / 10.0, 1) +
" A </span> <button onclick='editMaxChargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Max charge speed: " +
String(datalayer.battery.settings.max_user_set_charge_dA / 10.0, 1) +
" A </span> <button onclick='editMaxChargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Max discharge speed: " +
String(datalayer.battery.info.max_discharge_amp_dA / 10.0, 1) +
String(datalayer.battery.settings.max_user_set_discharge_dA / 10.0, 1) +
" A </span> <button onclick='editMaxDischargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Manual charge voltage limits: <span id='BATTERY_USE_VOLTAGE_LIMITS'>" +
String(datalayer.battery.settings.user_set_voltage_limits_active
? "<span>&#10003;</span>"
: "<span style='color: red;'>&#10005;</span>") +
"</span> <button onclick='editUseVoltageLimit()'>Edit</button></h4>";
content +=
"<h4 style='color: " +
String(datalayer.battery.settings.user_set_voltage_limits_active ? "white" : "darkgrey") +
";'>Target charge voltage: " + String(datalayer.battery.settings.max_user_set_charge_voltage_dV / 10.0, 1) +
" V </span> <button onclick='editMaxChargeVoltage()'>Edit</button></h4>";
content += "<h4 style='color: " +
String(datalayer.battery.settings.user_set_voltage_limits_active ? "white" : "darkgrey") +
";'>Target discharge voltage: " +
String(datalayer.battery.settings.max_user_set_discharge_voltage_dV / 10.0, 1) +
" V </span> <button onclick='editMaxDischargeVoltage()'>Edit</button></h4>";
// Close the block
content += "</div>";
@ -124,7 +145,9 @@ String settings_processor(const String& var) {
"updateBatterySize?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 1 "
"and 120000.');}}}";
content +=
"function editUseScaledSOC(){var value=prompt('Should SOC% be scaled? (0 = No, 1 = "
"function editUseScaledSOC(){var value=prompt('Extends battery life by rescaling the SOC within the configured "
"minimum "
"and maximum percentage. Should SOC scaling be applied? (0 = No, 1 = "
"Yes):');if(value!==null){if(value==0||value==1){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateUseScaledSOC?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
@ -155,6 +178,33 @@ String settings_processor(const String& var) {
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateMaxDischargeA?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between 0 "
"and 1000.0');}}}";
content +=
"function editUseVoltageLimit(){var value=prompt('Enable this option to manually restrict charge/discharge to "
"a specific voltage set below."
"If disabled the emulator automatically determines this based on battery limits. Restrict manually? (0 = No, 1 "
"= Yes)"
":');if(value!==null){if(value==0||value==1){var xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateUseVoltageLimit?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value between "
"0 "
"and 1.');}}}";
content +=
"function editMaxChargeVoltage(){var value=prompt('Some inverters needs to be artificially limited. Enter new "
"voltage setpoint batttery should charge to (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var "
"xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateMaxChargeVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
"between 0 "
"and 1000.0');}}}";
content +=
"function editMaxDischargeVoltage(){var value=prompt('Some inverters needs to be artificially limited. Enter "
"new "
"voltage setpoint batttery should discharge to (0-1000.0):');if(value!==null){if(value>=0&&value<=1000){var "
"xhr=new "
"XMLHttpRequest();xhr.onload=editComplete;xhr.onerror=editError;xhr.open('GET','/"
"updateMaxDischargeVoltage?value='+value,true);xhr.send();}else{alert('Invalid value. Please enter a value "
"between 0 "
"and 1000.0');}}}";
#ifdef TEST_FAKE_BATTERY
content +=
@ -201,7 +251,6 @@ String settings_processor(const String& var) {
#endif
content += "</script>";
content += "<button onclick='goToMainPage()'>Back to main page</button>";
content += "<script>";
content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>";
@ -222,7 +271,7 @@ const char* getCANInterfaceName(CAN_Interface interface) {
#endif
case CAN_ADDON_MCP2515:
return "Add-on CAN via GPIO MCP2515";
case CAN_ADDON_FD_MCP2518:
case CANFD_ADDON_MCP2518:
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
return "Add-on CAN-FD via GPIO MCP2518 (Classic CAN)";
#else

View file

@ -1,6 +1,9 @@
#include "webserver.h"
#include <Preferences.h>
#include <ctime>
#include "../../../USER_SECRETS.h"
#include "../../datalayer/datalayer.h"
#include "../../datalayer/datalayer_extended.h"
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
#include "../utils/events.h"
#include "../utils/led_handler.h"
@ -12,44 +15,20 @@ AsyncWebServer server(80);
// Measure OTA progress
unsigned long ota_progress_millis = 0;
#include "advanced_battery_html.h"
#include "can_logging_html.h"
#include "cellmonitor_html.h"
#include "debug_logging_html.h"
#include "events_html.h"
#include "index_html.cpp"
#include "settings_html.h"
enum WifiState {
INIT, //before connecting first time
RECONNECTING, //we've connected before, but lost connection
CONNECTED //we are connected
};
WifiState wifi_state = INIT;
MyTimer ota_timeout_timer = MyTimer(15000);
bool ota_active = false;
unsigned const long WIFI_MONITOR_INTERVAL_TIME = 15000;
unsigned const long INIT_WIFI_CONNECT_TIMEOUT = 8000; // Timeout for initial WiFi connect in milliseconds
unsigned const long DEFAULT_WIFI_RECONNECT_INTERVAL = 1000; // Default WiFi reconnect interval in ms
unsigned const long MAX_WIFI_RETRY_INTERVAL = 90000; // Maximum wifi retry interval in ms
unsigned long last_wifi_monitor_time = millis(); //init millis so wifi monitor doesn't run immediately
unsigned long wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor doesn't run immediately
const char get_firmware_info_html[] = R"rawliteral(%X%)rawliteral";
void init_webserver() {
// Configure WiFi
#ifdef WIFIAP
if (AccessPointEnabled) {
WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection
init_WiFi_AP();
} else {
WiFi.mode(WIFI_STA); // Only Router connection
}
#else
WiFi.mode(WIFI_STA); // Only Router connection
#endif // WIFIAP
init_WiFi_STA(ssid.c_str(), password.c_str(), wifi_channel);
String content = index_html;
@ -76,6 +55,83 @@ void init_webserver() {
request->send_P(200, "text/html", index_html, settings_processor);
});
// Route for going to advanced battery info web page
server.on("/advanced", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send_P(200, "text/html", index_html, advanced_battery_processor);
});
// Route for going to CAN logging web page
server.on("/canlog", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send_P(200, "text/html", index_html, can_logger_processor);
});
#ifdef DEBUG_VIA_WEB
// Route for going to debug logging web page
server.on("/log", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send_P(200, "text/html", index_html, debug_logger_processor);
});
#endif // DEBUG_VIA_WEB
// Define the handler to stop can logging
server.on("/stop_can_logging", HTTP_GET, [](AsyncWebServerRequest* request) {
datalayer.system.info.can_logging_active = false;
request->send_P(200, "text/plain", "Logging stopped");
});
// Define the handler to export can log
server.on("/export_can_log", HTTP_GET, [](AsyncWebServerRequest* request) {
String logs = String(datalayer.system.info.logged_can_messages);
if (logs.length() == 0) {
logs = "No logs available.";
}
// Get the current time
time_t now = time(nullptr);
struct tm timeinfo;
localtime_r(&now, &timeinfo);
// Ensure time retrieval was successful
char filename[32];
if (strftime(filename, sizeof(filename), "canlog_%H-%M-%S.txt", &timeinfo)) {
// Valid filename created
} else {
// Fallback filename if automatic timestamping failed
strcpy(filename, "battery_emulator_can_log.txt");
}
// Use request->send with dynamic headers
AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", logs);
response->addHeader("Content-Disposition", String("attachment; filename=\"") + String(filename) + "\"");
request->send(response);
});
// Define the handler to export debug log
server.on("/export_log", HTTP_GET, [](AsyncWebServerRequest* request) {
String logs = String(datalayer.system.info.logged_can_messages);
if (logs.length() == 0) {
logs = "No logs available.";
}
// Get the current time
time_t now = time(nullptr);
struct tm timeinfo;
localtime_r(&now, &timeinfo);
// Ensure time retrieval was successful
char filename[32];
if (strftime(filename, sizeof(filename), "log_%H-%M-%S.txt", &timeinfo)) {
// Valid filename created
} else {
// Fallback filename if automatic timestamping failed
strcpy(filename, "battery_emulator_log.txt");
}
// Use request->send with dynamic headers
AsyncWebServerResponse* response = request->beginResponse(200, "text/plain", logs);
response->addHeader("Content-Disposition", String("attachment; filename=\"") + String(filename) + "\"");
request->send(response);
});
// Route for going to cellmonitor web page
server.on("/cellmonitor", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
@ -90,6 +146,18 @@ void init_webserver() {
request->send_P(200, "text/html", index_html, events_processor);
});
// Route for clearing all events
server.on("/clearevents", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
reset_all_events();
// Send back a response that includes an instant redirect to /events
String response = "<html><body>";
response += "<script>window.location.href = '/events';</script>"; // Instant redirect
response += "</body></html>";
request->send(200, "text/html", response);
});
// Route for editing SSID
server.on("/updateSSID", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
@ -99,7 +167,7 @@ void init_webserver() {
String value = request->getParam("value")->value();
if (value.length() <= 63) { // Check if SSID is within the allowable length
ssid = value.c_str();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "SSID must be 63 characters or less");
@ -116,7 +184,7 @@ void init_webserver() {
String value = request->getParam("value")->value();
if (value.length() > 8) { // Check if password is within the allowable length
password = value.c_str();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Password must be atleast 8 characters");
@ -133,7 +201,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.info.total_capacity_Wh = value.toInt();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -147,7 +215,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.soc_scaling_active = value.toInt();
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -161,7 +229,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_percentage = static_cast<uint16_t>(value.toFloat() * 100);
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -181,6 +249,23 @@ void init_webserver() {
}
});
// Route for equipment stop/resume
server.on("/equipmentStop", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("stop")) {
String valueStr = request->getParam("stop")->value();
if (valueStr == "true" || valueStr == "1") {
setBatteryPause(true, false, true);
} else {
setBatteryPause(false, false, false);
}
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing SOCMin
server.on("/updateSocMin", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
@ -188,7 +273,7 @@ void init_webserver() {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.min_percentage = static_cast<uint16_t>(value.toFloat() * 100);
storeSettings();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -201,8 +286,8 @@ void init_webserver() {
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.info.max_charge_amp_dA = static_cast<uint16_t>(value.toFloat() * 10);
storeSettings();
datalayer.battery.settings.max_user_set_charge_dA = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
@ -215,14 +300,65 @@ void init_webserver() {
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.info.max_discharge_amp_dA = static_cast<uint16_t>(value.toFloat() * 10);
storeSettings();
datalayer.battery.settings.max_user_set_discharge_dA = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing BATTERY_USE_VOLTAGE_LIMITS
server.on("/updateUseVoltageLimit", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.user_set_voltage_limits_active = value.toInt();
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing MaxChargeVoltage
server.on("/updateMaxChargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_user_set_charge_voltage_dV = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing MaxDischargeVoltage
server.on("/updateMaxDischargeVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.settings.max_user_set_discharge_voltage_dV = static_cast<uint16_t>(value.toFloat() * 10);
store_settings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// 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)) {
return request->requestAuthentication();
}
datalayer_extended.nissanleaf.UserRequestSOHreset = true;
request->send(200, "text/plain", "Updated successfully");
});
#ifdef TEST_FAKE_BATTERY
// Route for editing FakeBatteryVoltage
server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
@ -277,7 +413,7 @@ void init_webserver() {
String value = request->getParam("value")->value();
float val = value.toFloat();
if (!(val <= datalayer.battery.info.max_charge_amp_dA && val <= CHARGER_MAX_A)) {
if (!(val <= datalayer.battery.settings.max_user_set_charge_dA && val <= CHARGER_MAX_A)) {
request->send(400, "text/plain", "Bad Request");
}
@ -342,7 +478,10 @@ void init_webserver() {
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password))
return request->requestAuthentication();
request->send(200, "text/plain", "Rebooting server...");
//TODO: Should we handle contactors gracefully? Ifdef CONTACTOR_CONTROL then what?
//Equipment STOP without persisting the equipment state before restart
// Max Charge/Discharge = 0; CAN = stop; contactors = open
setBatteryPause(true, true, true, false);
delay(1000);
ESP.restart();
});
@ -352,29 +491,8 @@ void init_webserver() {
// Start server
server.begin();
#ifdef MQTT
// Init MQTT
init_mqtt();
#endif // MQTT
}
#ifdef WIFIAP
void init_WiFi_AP() {
#ifdef DEBUG_VIA_USB
Serial.println("Creating Access Point: " + String(ssidAP));
Serial.println("With password: " + String(passwordAP));
#endif // DEBUG_VIA_USB
WiFi.softAP(ssidAP, passwordAP);
IPAddress IP = WiFi.softAPIP();
#ifdef DEBUG_VIA_USB
Serial.println("Access Point created.");
Serial.print("IP address: ");
Serial.println(IP);
#endif // DEBUG_VIA_USB
}
#endif // WIFIAP
String getConnectResultString(wl_status_t status) {
switch (status) {
case WL_CONNECTED:
@ -398,42 +516,7 @@ String getConnectResultString(wl_status_t status) {
}
}
void wifi_monitor() {
unsigned long currentMillis = millis();
if (currentMillis - last_wifi_monitor_time > WIFI_MONITOR_INTERVAL_TIME) {
last_wifi_monitor_time = currentMillis;
wl_status_t status = WiFi.status();
if (status != WL_CONNECTED && status != WL_IDLE_STATUS) {
#ifdef DEBUG_VIA_USB
Serial.println(getConnectResultString(status));
#endif // DEBUG_VIA_USB
if (wifi_state == INIT) { //we haven't been connected yet, try the init logic
init_WiFi_STA(ssid.c_str(), password.c_str(), wifi_channel);
} else { //we were connected before, try the reconnect logic
if (currentMillis - last_wifi_attempt_time > wifi_reconnect_interval) {
last_wifi_attempt_time = currentMillis;
#ifdef DEBUG_VIA_USB
Serial.println("WiFi not connected, trying to reconnect...");
#endif // DEBUG_VIA_USB
wifi_state = RECONNECTING;
WiFi.reconnect();
wifi_reconnect_interval = min(wifi_reconnect_interval * 2, MAX_WIFI_RETRY_INTERVAL);
}
}
} else if (status == WL_CONNECTED && wifi_state != CONNECTED) {
wifi_state = CONNECTED;
wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
// Print local IP address and start web server
#ifdef DEBUG_VIA_USB
Serial.print("Connected to WiFi network: " + String(ssid.c_str()));
Serial.print(" IP address: " + WiFi.localIP().toString());
Serial.print(" Signal Strength: " + String(WiFi.RSSI()) + " dBm");
Serial.println(" Channel: " + String(WiFi.channel()));
Serial.println(" Hostname: " + String(WiFi.getHostname()));
#endif // DEBUG_VIA_USB
}
}
void ota_monitor() {
if (ota_active && ota_timeout_timer.elapsed()) {
// OTA timeout, try to restore can and clear the update event
set_event(EVENT_OTA_UPDATE_TIMEOUT, 0);
@ -441,20 +524,6 @@ void wifi_monitor() {
}
}
void init_WiFi_STA(const char* ssid, const char* password, const uint8_t wifi_channel) {
// Connect to Wi-Fi network with SSID and password
#ifdef DEBUG_VIA_USB
Serial.print("Connecting to ");
Serial.println(ssid);
#endif // DEBUG_VIA_USB
WiFi.begin(ssid, password, wifi_channel);
WiFi.setAutoReconnect(true); // Enable auto reconnect
wl_status_t result = static_cast<wl_status_t>(WiFi.waitForConnectResult(INIT_WIFI_CONNECT_TIMEOUT));
if (result) {
//TODO: Add event or serial print?
}
}
// Function to initialize ElegantOTA
void init_ElegantOTA() {
ElegantOTA.begin(&server); // Start ElegantOTA
@ -474,6 +543,9 @@ String get_firmware_info_processor(const String& var) {
#ifdef HW_STARK
doc["hardware"] = "Stark CMR Module";
#endif // HW_STARK
#ifdef HW_3LB
doc["hardware"] = "3LB board";
#endif // HW_STARK
doc["firmware"] = String(version_number);
serializeJson(doc, content);
@ -489,20 +561,25 @@ String processor(const String& var) {
//Page format
content += "<style>";
content += "body { background-color: black; color: white; }";
content +=
"button { background-color: #505E67; color: white; border: none; padding: 10px 20px; margin-bottom: 20px; "
"cursor: pointer; border-radius: 10px; }";
content += "button:hover { background-color: #3A4A52; }";
content += "</style>";
// Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
// Show version number
content += "<h4>Software: " + String(version_number) + "</h4>";
content += "<h4>Software: " + String(version_number);
// Show hardware used:
#ifdef HW_LILYGO
content += "<h4>Hardware: LilyGo T-CAN485</h4>";
content += " Hardware: LilyGo T-CAN485";
#endif // HW_LILYGO
#ifdef HW_STARK
content += "<h4>Hardware: Stark CMR Module</h4>";
content += " Hardware: Stark CMR Module";
#endif // HW_STARK
content += "</h4>";
content += "<h4>Uptime: " + uptime_formatter::getUptime() + "</h4>";
#ifdef FUNCTION_TIME_MEASUREMENT
// Load information
@ -518,7 +595,7 @@ String processor(const String& var) {
"<h4>loop() task max load last 10 s: " + String(datalayer.system.status.loop_task_10s_max_us) + " us</h4>";
content += "<h4>Max load @ worst case execution of core task:</h4>";
content += "<h4>10ms function timing: " + String(datalayer.system.status.time_snap_10ms_us) + " us</h4>";
content += "<h4>5s function timing: " + String(datalayer.system.status.time_snap_5s_us) + " us</h4>";
content += "<h4>Values function timing: " + String(datalayer.system.status.time_snap_values_us) + " us</h4>";
content += "<h4>CAN/serial RX function timing: " + String(datalayer.system.status.time_snap_comm_us) + " us</h4>";
content += "<h4>CAN TX function timing: " + String(datalayer.system.status.time_snap_cantx_us) + " us</h4>";
content += "<h4>OTA function timing: " + String(datalayer.system.status.time_snap_ota_us) + " us</h4>";
@ -526,11 +603,14 @@ String processor(const String& var) {
wl_status_t status = WiFi.status();
// Display ssid of network connected to and, if connected to the WiFi, its own IP
content += "<h4>SSID: " + String(ssid.c_str()) + "</h4>";
content += "<h4>SSID: " + String(ssid.c_str());
if (status == WL_CONNECTED) {
// Get and display the signal strength (RSSI) and channel
content += " RSSI:" + String(WiFi.RSSI()) + " dBm Ch: " + String(WiFi.channel());
}
content += "</h4>";
if (status == WL_CONNECTED) {
content += "<h4>IP: " + WiFi.localIP().toString() + "</h4>";
// Get and display the signal strength (RSSI) and channel
content += "<h4>Signal strength: " + String(WiFi.RSSI()) + " dBm, at channel " + String(WiFi.channel()) + "</h4>";
} else {
content += "<h4>Wifi state: " + getConnectResultString(status) + "</h4>";
}
@ -542,96 +622,16 @@ String processor(const String& var) {
// Display which components are used
content += "<h4 style='color: white;'>Inverter protocol: ";
#ifdef BYD_CAN
content += "BYD Battery-Box Premium HVS over CAN Bus";
#endif // BYD_CAN
#ifdef BYD_MODBUS
content += "BYD 11kWh HVM battery over Modbus RTU";
#endif // BYD_MODBUS
#ifdef PYLON_CAN
content += "Pylontech battery over CAN bus";
#endif // PYLON_CAN
#ifdef SERIAL_LINK_TRANSMITTER
content += "Serial link to another LilyGo board";
#endif // SERIAL_LINK_TRANSMITTER
#ifdef SMA_CAN
content += "BYD Battery-Box H 8.9kWh, 7 mod over CAN bus";
#endif // SMA_CAN
#ifdef SOFAR_CAN
content += "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame) over CAN bus";
#endif // SOFAR_CAN
#ifdef SOLAX_CAN
content += "SolaX Triple Power LFP over CAN bus";
#endif // SOLAX_CAN
content += datalayer.system.info.inverter_protocol;
content += "</h4>";
content += "<h4 style='color: white;'>Battery protocol: ";
#ifdef BMW_I3_BATTERY
content += "BMW i3";
#endif // BMW_I3_BATTERY
#ifdef BYD_ATTO_3_BATTERY
content += "BYD Atto 3";
#endif // BYD_ATTO_3_BATTERY
#ifdef CHADEMO_BATTERY
content += "Chademo V2X mode";
#endif // CHADEMO_BATTERY
#ifdef IMIEV_CZERO_ION_BATTERY
content += "I-Miev / C-Zero / Ion Triplet";
#endif // IMIEV_CZERO_ION_BATTERY
#ifdef JAGUAR_IPACE_BATTERY
content += "Jaguar I-PACE";
#endif // JAGUAR_IPACE_BATTERY
#ifdef KIA_HYUNDAI_64_BATTERY
content += "Kia/Hyundai 64kWh";
#endif // KIA_HYUNDAI_64_BATTERY
#ifdef KIA_E_GMP_BATTERY
content += "Kia/Hyundai EGMP platform";
#endif // KIA_E_GMP_BATTERY
#ifdef KIA_HYUNDAI_HYBRID_BATTERY
content += "Kia/Hyundai Hybrid";
#endif // KIA_HYUNDAI_HYBRID_BATTERY
#ifdef MG_5_BATTERY
content += "MG 5";
#endif // MG_5_BATTERY
#ifdef NISSAN_LEAF_BATTERY
content += "Nissan LEAF";
#endif // NISSAN_LEAF_BATTERY
#ifdef RJXZS_BMS
content += "RJXZS BMS, DIY battery";
#endif // RJXZS_BMS
#ifdef RENAULT_KANGOO_BATTERY
content += "Renault Kangoo";
#endif // RENAULT_KANGOO_BATTERY
#ifdef RENAULT_ZOE_GEN1_BATTERY
content += "Renault Zoe Gen1 22/40";
#endif // RENAULT_ZOE_GEN1_BATTERY
#ifdef RENAULT_ZOE_GEN2_BATTERY
content += "Renault Zoe Gen2 50";
#endif // RENAULT_ZOE_GEN2_BATTERY
#ifdef SANTA_FE_PHEV_BATTERY
content += "Santa Fe PHEV";
#endif // SANTA_FE_PHEV_BATTERY
#ifdef SERIAL_LINK_RECEIVER
content += "Serial link to another LilyGo board";
#endif // SERIAL_LINK_RECEIVER
#ifdef TESLA_MODEL_SX_BATTERY
content += "Tesla Model S/X";
#endif // TESLA_MODEL_SX_BATTERY
#ifdef TESLA_MODEL_3Y_BATTERY
content += "Tesla Model 3/Y";
#endif // TESLA_MODEL_3Y_BATTERY
#ifdef VOLVO_SPA_BATTERY
content += "Volvo / Polestar 78kWh battery";
#endif // VOLVO_SPA_BATTERY
#ifdef TEST_FAKE_BATTERY
content += "Fake battery for testing purposes";
#endif // TEST_FAKE_BATTERY
content += datalayer.system.info.battery_protocol;
#ifdef DOUBLE_BATTERY
content += " (Double battery)";
#endif // DOUBLE_BATTERY
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
content += " (LFP)";
}
#endif // DOUBLE_BATTERY
content += "</h4>";
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
@ -693,6 +693,12 @@ String processor(const String& var) {
float powerFloat = static_cast<float>(datalayer.battery.status.active_power_W); // Convert to float
float tempMaxFloat = static_cast<float>(datalayer.battery.status.temperature_max_dC) / 10.0; // Convert to float
float tempMinFloat = static_cast<float>(datalayer.battery.status.temperature_min_dC) / 10.0; // Convert to float
float maxCurrentChargeFloat =
static_cast<float>(datalayer.battery.status.max_charge_current_dA) / 10.0; // Convert to float
float maxCurrentDischargeFloat =
static_cast<float>(datalayer.battery.status.max_discharge_current_dA) / 10.0; // Convert to float
uint16_t cell_delta_mv =
datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
content += "<h4 style='color: white;'>Real SOC: " + String(socRealFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) + "</h4>";
@ -701,20 +707,55 @@ String processor(const String& var) {
content += "<h4 style='color: white;'>Current: " + String(currentFloat, 1) + " A</h4>";
content += formatPowerValue("Power", powerFloat, "", 1);
content += formatPowerValue("Total capacity", datalayer.battery.info.total_capacity_Wh, "h", 0);
content += formatPowerValue("Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1);
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
content += formatPowerValue("Real Remaining capacity", datalayer.battery.status.remaining_capacity_Wh, "h", 1);
content +=
formatPowerValue("Scaled Remaining capacity", datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1);
if (datalayer.system.settings.equipment_stop_active) {
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red");
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red");
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
} else {
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
}
content += "<h4>Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
content += "<h4>Cell min: " + String(datalayer.battery.status.cell_min_voltage_mV) + " mV</h4>";
if (cell_delta_mv > datalayer.battery.info.max_cell_voltage_deviation_mV) {
content += "<h4 style='color: red;'>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
} else {
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
}
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
if (datalayer.battery.status.bms_status == ACTIVE) {
content += "<h4>System status: OK </h4>";
} else if (datalayer.battery.status.bms_status == UPDATING) {
content += "<h4>System status: UPDATING </h4>";
} else {
content += "<h4>System status: FAULT </h4>";
content += "<h4>System status: ";
switch (datalayer.battery.status.bms_status) {
case ACTIVE:
content += String("OK");
break;
case UPDATING:
content += String("UPDATING");
break;
case FAULT:
content += String("FAULT");
break;
case INACTIVE:
content += String("INACTIVE");
break;
case STANDBY:
content += String("STANDBY");
break;
default:
content += String("??");
break;
}
content += "</h4>";
if (datalayer.battery.status.current_dA == 0) {
content += "<h4>Battery idle</h4>";
} else if (datalayer.battery.status.current_dA < 0) {
@ -738,9 +779,49 @@ String processor(const String& var) {
content += "<span style='color: red;'>&#10005;</span></h4>";
}
if (emulator_pause_status == NORMAL)
content += "<h4>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
else
content += "<h4 style='color: red;'>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
#ifdef CONTACTOR_CONTROL
content += "<h4>Contactors controlled by emulator, state: ";
if (datalayer.system.status.contactors_engaged) {
content += "<span style='color: green;'>ON</span>";
} else {
content += "<span style='color: red;'>OFF</span>";
}
content += "</h4>";
content += "<h4>Precharge: (";
content += PRECHARGE_TIME_MS;
content += " ms) Cont. Neg.: ";
#ifdef PWM_CONTACTOR_CONTROL
if (datalayer.system.status.contactors_engaged) {
content += "<span style='color: green;'>Economized</span>";
content += " Cont. Pos.: ";
content += "<span style='color: green;'>Economized</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
content += " Cont. Pos.: ";
content += "<span style='color: red;'>&#10005;</span>";
}
#else // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Pos.: ";
if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
#endif //no PWM_CONTACTOR_CONTROL
content += "</h4>";
#endif
// Close the block
content += "</div>";
@ -773,6 +854,7 @@ String processor(const String& var) {
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
cell_delta_mv = datalayer.battery2.status.cell_max_voltage_mV - datalayer.battery2.status.cell_min_voltage_mV;
content += "<h4 style='color: white;'>Real SOC: " + String(socRealFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) + "</h4>";
@ -781,11 +863,29 @@ String processor(const String& var) {
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 += formatPowerValue("Real Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
content +=
formatPowerValue("Scaled Remaining capacity", datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1);
if (datalayer.system.settings.equipment_stop_active) {
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red");
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1, "red");
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
} else {
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 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
}
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>";
if (cell_delta_mv > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
content += "<h4 style='color: red;'>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
} else {
content += "<h4>Cell delta: " + String(cell_delta_mv) + " mV</h4>";
}
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
if (datalayer.battery.status.bms_status == ACTIVE) {
@ -817,10 +917,50 @@ String processor(const String& var) {
} else {
content += "<span style='color: red;'>&#10005;</span></h4>";
}
if (emulator_pause_status == NORMAL)
content += "<h4>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
else
content += "<h4 style='color: red;'>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
#ifdef CONTACTOR_CONTROL
content += "<h4>Contactors controlled by emulator, state: ";
if (datalayer.system.status.contactors_battery2_engaged) {
content += "<span style='color: green;'>ON</span>";
} else {
content += "<span style='color: red;'>OFF</span>";
}
content += "</h4>";
#ifdef CONTACTOR_CONTROL_DOUBLE_BATTERY
content += "<h4>Cont. Neg.: ";
#ifdef PWM_CONTACTOR_CONTROL
if (datalayer.system.status.contactors_battery2_engaged) {
content += "<span style='color: green;'>Economized</span>";
content += " Cont. Pos.: ";
content += "<span style='color: green;'>Economized</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
content += " Cont. Pos.: ";
content += "<span style='color: red;'>&#10005;</span>";
}
#else // No PWM_CONTACTOR_CONTROL , we can read the pin and see feedback. Helpful if channel overloaded
if (digitalRead(SECOND_NEGATIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Pos.: ";
if (digitalRead(SECOND_POSITIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
#endif //no PWM_CONTACTOR_CONTROL
content += "</h4>";
#endif // CONTACTOR_CONTROL_DOUBLE_BATTERY
#endif // CONTACTOR_CONTROL
content += "</div>";
content += "</div>";
@ -883,25 +1023,46 @@ String processor(const String& var) {
#endif // defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
if (emulator_pause_request_ON)
content += "<button onclick='PauseBattery(false)'>Resume Battery</button>";
content += "<button onclick='PauseBattery(false)'>Resume charge/discharge</button> ";
else
content += "<button onclick='PauseBattery(true)'>Pause Battery</button>";
content +=
"<button onclick=\"if(confirm('Are you sure you want to pause charging and discharging? This will set the "
"maximum charge and discharge values to zero, preventing any further power flow.')) { PauseBattery(true); "
"}\">Pause charge/discharge</button> ";
content += "<button onclick='OTA()'>Perform OTA update</button>";
content += " ";
content += "<button onclick='Settings()'>Change Settings</button>";
content += " ";
content += "<button onclick='Cellmon()'>Cellmonitor</button>";
content += " ";
content += "<button onclick='Events()'>Events</button>";
content += " ";
content += "<button onclick='OTA()'>Perform OTA update</button> ";
content += "<button onclick='Settings()'>Change Settings</button> ";
content += "<button onclick='Advanced()'>More Battery Info</button> ";
content += "<button onclick='CANlog()'>CAN logger</button> ";
#ifdef DEBUG_VIA_WEB
content += "<button onclick='Log()'>Log</button> ";
#endif // DEBUG_VIA_WEB
content += "<button onclick='Cellmon()'>Cellmonitor</button> ";
content += "<button onclick='Events()'>Events</button> ";
content += "<button onclick='askReboot()'>Reboot Emulator</button>";
if (WEBSERVER_AUTH_REQUIRED)
content += "<button onclick='logout()'>Logout</button>";
if (!datalayer.system.settings.equipment_stop_active)
content +=
"<br/><br/><button style=\"background:red;color:white;cursor:pointer;\""
" onclick=\""
"if(confirm('This action will open contactors on the battery and stop all CAN communications. Are you "
"sure?')) { estop(true); }\""
">Open Contactors</button><br/>";
else
content +=
"<br/><br/><button style=\"background:green;color:white;cursor:pointer;\""
"20px;font-size:16px;font-weight:bold;cursor:pointer;border-radius:5px; margin:10px;"
" onclick=\""
"if(confirm('This action will restore the battery state. Are you sure?')) { estop(false); }\""
">Close Contactors</button><br/>";
content += "<script>";
content += "function OTA() { window.location.href = '/update'; }";
content += "function Cellmon() { window.location.href = '/cellmonitor'; }";
content += "function Settings() { window.location.href = '/settings'; }";
content += "function Advanced() { window.location.href = '/advanced'; }";
content += "function CANlog() { window.location.href = '/canlog'; }";
content += "function Log() { window.location.href = '/log'; }";
content += "function Events() { window.location.href = '/events'; }";
content +=
"function askReboot() { if (window.confirm('Are you sure you want to reboot the emulator? NOTE: If "
@ -926,7 +1087,12 @@ String processor(const String& var) {
"XMLHttpRequest();xhr.onload=function() { "
"window.location.reload();};xhr.open('GET','/pause?p='+pause,true);xhr.send();";
content += "}";
content += "function estop(stop){";
content +=
"var xhr=new "
"XMLHttpRequest();xhr.onload=function() { "
"window.location.reload();};xhr.open('GET','/equipmentStop?stop='+stop,true);xhr.send();";
content += "}";
content += "</script>";
//Script for refreshing page
@ -949,6 +1115,7 @@ void onOTAStart() {
// If already set, make a new attempt
clear_event(EVENT_OTA_UPDATE_TIMEOUT);
ota_active = true;
ota_timeout_timer.reset();
}
@ -956,9 +1123,9 @@ void onOTAProgress(size_t current, size_t final) {
// Log every 1 second
if (millis() - ota_progress_millis > 1000) {
ota_progress_millis = millis();
#ifdef DEBUG_VIA_USB
Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
#endif // DEBUG_LOG
// Reset the "watchdog"
ota_timeout_timer.reset();
}
@ -971,22 +1138,25 @@ void onOTAEnd(bool success) {
// Log when OTA has finished
if (success) {
//Equipment STOP without persisting the equipment state before restart
// Max Charge/Discharge = 0; CAN = stop; contactors = open
setBatteryPause(true, true, true, false);
// a reboot will be done by the OTA library. no need to do anything here
#ifdef DEBUG_VIA_USB
Serial.println("OTA update finished successfully!");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("OTA update finished successfully!");
#endif // DEBUG_LOG
} else {
#ifdef DEBUG_VIA_USB
Serial.println("There was an error during OTA update!");
#endif // DEBUG_VIA_USB
#ifdef DEBUG_LOG
logging.println("There was an error during OTA update!");
#endif // DEBUG_LOG
//try to Resume the battery pause and CAN communication
setBatteryPause(false, false);
}
}
template <typename T> // This function makes power values appear as W when under 1000, and kW when over
String formatPowerValue(String label, T value, String unit, int precision) {
String result = "<h4 style='color: white;'>" + label + ": ";
String formatPowerValue(String label, T value, String unit, int precision, String color) {
String result = "<h4 style='color: " + color + ";'>" + label + ": ";
if (std::is_same<T, float>::value || std::is_same<T, uint16_t>::value || std::is_same<T, uint32_t>::value) {
float convertedValue = static_cast<float>(value);

View file

@ -6,27 +6,18 @@
#include "../../include.h"
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h"
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
#ifdef MQTT
#include "../../lib/knolleary-pubsubclient/PubSubClient.h"
#endif
#include "../../lib/me-no-dev-AsyncTCP/src/AsyncTCP.h"
#include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#ifdef MQTT
#include "../mqtt/mqtt.h"
#endif
extern const char* version_number; // The current software version, shown on webserver
#include <string>
extern std::string ssid;
extern std::string password;
extern const uint8_t wifi_channel;
extern const char* ssidAP;
extern const char* passwordAP;
extern const char* http_username;
extern const char* http_password;
extern const char* ssidAP;
// Common charger parameters
extern float charger_stat_HVcur;
extern float charger_stat_HVvol;
@ -47,36 +38,6 @@ extern uint16_t OBC_Charge_Power;
*/
void init_webserver();
/**
* @brief Monitoring loop for WiFi. Will attempt to reconnect to access point if the connection goes down.
*
* @param[in] void
*
* @return void
*/
void wifi_monitor();
#ifdef WIFIAP
/**
* @brief Initialization function that creates a WiFi Access Point.
*
* @param[in] void
*
* @return void
*/
void init_WiFi_AP();
#endif // WIFIAP
/**
* @brief Initialization function that connects to an existing network.
*
* @param[in] ssid WiFi network name
* @param[in] password WiFi network password
*
* @return void
*/
void init_WiFi_STA(const char* ssid, const char* password, const uint8_t channel);
// /**
// * @brief Function to handle WiFi reconnection.
// *
@ -141,8 +102,10 @@ void onOTAEnd(bool success);
* @return string: values
*/
template <typename T>
String formatPowerValue(String label, T value, String unit, int precision);
String formatPowerValue(String label, T value, String unit, int precision, String color = "white");
extern void storeSettings();
extern void store_settings();
void ota_monitor();
#endif

View file

@ -0,0 +1,212 @@
#include "wifi.h"
#include "../../include.h"
#include "../utils/events.h"
// Configuration Parameters
static const uint16_t WIFI_CHECK_INTERVAL = 2000; // 1 seconds normal check interval when last connected
static const uint16_t STEP_WIFI_CHECK_INTERVAL = 2000; // 3 seconds wait step increase in checks for normal reconnects
static const uint16_t MAX_STEP_WIFI_CHECK_INTERVAL =
10000; // 15 seconds wait step increase in checks for normal reconnects
static const uint16_t INIT_WIFI_FULL_RECONNECT_INTERVAL =
10000; // 10 seconds starting wait interval for full reconnects and first connection
static const uint16_t MAX_WIFI_FULL_RECONNECT_INTERVAL = 60000; // 60 seconds maximum wait interval for full reconnects
static const uint16_t STEP_WIFI_FULL_RECONNECT_INTERVAL =
5000; // 5 seconds wait step increase in checks for full reconnects
static const uint16_t MAX_RECONNECT_ATTEMPTS =
3; // Maximum number of reconnect attempts before forcing a full connection
// State variables
static unsigned long lastReconnectAttempt = 0;
static unsigned long lastWiFiCheck = 0;
static bool hasConnectedBefore = false;
static uint16_t reconnectAttempts = 0; // Counter for reconnect attempts
static uint16_t current_full_reconnect_interval = INIT_WIFI_FULL_RECONNECT_INTERVAL;
static uint16_t current_check_interval = WIFI_CHECK_INTERVAL;
static bool connected_once = false;
void init_WiFi() {
#ifdef WIFIAP
WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection
init_WiFi_AP();
#else
WiFi.mode(WIFI_STA); // Only Router connection
#endif // WIFIAP
// Set WiFi to auto reconnect
WiFi.setAutoReconnect(true);
#ifdef WIFICONFIG
// Set static IP
WiFi.config(local_IP, gateway, subnet);
#endif
// Initialize Wi-Fi event handlers
WiFi.onEvent(onWifiConnect, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED);
WiFi.onEvent(onWifiDisconnect, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
WiFi.onEvent(onWifiGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);
// Start Wi-Fi connection
connectToWiFi();
}
// Task to monitor Wi-Fi status and handle reconnections
void wifi_monitor() {
unsigned long currentMillis = millis();
// Check if it's time to monitor the Wi-Fi status
// WIFI_CHECK_INTERVAL for normal checks and INIT_WIFI_FULL_RECONNECT_INTERVAL for first connections or full connect attepts
if ((hasConnectedBefore && (currentMillis - lastWiFiCheck > current_check_interval)) ||
(!hasConnectedBefore && (currentMillis - lastWiFiCheck > INIT_WIFI_FULL_RECONNECT_INTERVAL))) {
lastWiFiCheck = currentMillis;
wl_status_t status = WiFi.status();
if (status != WL_CONNECTED) {
// Increase the current check interval if it's not at the maximum
if (current_check_interval + STEP_WIFI_CHECK_INTERVAL <= MAX_STEP_WIFI_CHECK_INTERVAL)
current_check_interval += STEP_WIFI_CHECK_INTERVAL;
#ifdef DEBUG_LOG
logging.println("Wi-Fi not connected, attempting to reconnect...");
#endif
// Try WiFi.reconnect() if it was successfully connected at least once
if (hasConnectedBefore) {
lastReconnectAttempt = millis(); // Reset reconnection attempt timer
#ifdef DEBUG_LOG
logging.println("Wi-Fi reconnect attempt...");
#endif
if (WiFi.reconnect()) {
#ifdef DEBUG_LOG
logging.println("Wi-Fi reconnect attempt sucess...");
#endif
reconnectAttempts = 0; // Reset the attempt counter on successful reconnect
} else {
#ifdef DEBUG_LOG
logging.println("Wi-Fi reconnect attempt error...");
#endif
reconnectAttempts++;
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
#ifdef DEBUG_LOG
logging.println("Failed to reconnect multiple times, forcing a full connection attempt...");
#endif
FullReconnectToWiFi();
}
}
} else {
// If no previous connection, force a full connection attempt
if (currentMillis - lastReconnectAttempt > current_full_reconnect_interval) {
#ifdef DEBUG_LOG
logging.println("No previous OK connection, force a full connection attempt...");
#endif
FullReconnectToWiFi();
}
}
}
}
}
// Function to force a full reconnect to Wi-Fi
static void FullReconnectToWiFi() {
// Increase the current reconnect interval if it's not at the maximum
if (current_full_reconnect_interval + STEP_WIFI_FULL_RECONNECT_INTERVAL <= MAX_WIFI_FULL_RECONNECT_INTERVAL) {
current_full_reconnect_interval += STEP_WIFI_FULL_RECONNECT_INTERVAL;
}
hasConnectedBefore = false; // Reset the flag to force a full reconnect
WiFi.disconnect(); //force disconnect from the current network
connectToWiFi(); //force a full connection attempt
}
// Function to handle Wi-Fi connection
static void connectToWiFi() {
if (WiFi.status() != WL_CONNECTED) {
lastReconnectAttempt = millis(); // Reset the reconnect attempt timer
#ifdef DEBUG_LOG
logging.println("Connecting to Wi-Fi...");
#endif
WiFi.begin(ssid.c_str(), password.c_str(), wifi_channel);
} else {
#ifdef DEBUG_LOG
logging.println("Wi-Fi already connected.");
#endif
}
}
// Event handler for successful Wi-Fi connection
static void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) {
clear_event(EVENT_WIFI_DISCONNECT);
set_event(EVENT_WIFI_CONNECT, 0);
connected_once = true;
#ifdef DEBUG_LOG
logging.print("Wi-Fi connected. RSSI: ");
logging.print(-WiFi.RSSI());
logging.print(" dBm, IP address: ");
logging.println(WiFi.localIP().toString());
#endif
hasConnectedBefore = true; // Mark as successfully connected at least once
reconnectAttempts = 0; // Reset the attempt counter
current_full_reconnect_interval = INIT_WIFI_FULL_RECONNECT_INTERVAL; // Reset the full reconnect interval
current_check_interval = WIFI_CHECK_INTERVAL; // Reset the full reconnect interval
clear_event(EVENT_WIFI_CONNECT);
}
// Event handler for Wi-Fi Got IP
static void onWifiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
//clear disconnects events if we got a IP
clear_event(EVENT_WIFI_DISCONNECT);
#ifdef DEBUG_LOG
logging.print("Wi-Fi Got IP. ");
logging.print("IP address: ");
logging.println(WiFi.localIP().toString());
#endif
}
// Event handler for Wi-Fi disconnection
static void onWifiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info) {
if (connected_once)
set_event(EVENT_WIFI_DISCONNECT, 0);
#ifdef DEBUG_LOG
logging.println("Wi-Fi disconnected.");
#endif
//we dont do anything here, the reconnect will be handled by the monitor
//too many events received when the connection is lost
//normal reconnect retry start at first 2 seconds
}
#ifdef MDNSRESPONDER
// Initialise mDNS
void init_mDNS() {
// Calulate the host name using the last two chars from the MAC address so each one is likely unique on a network.
// e.g batteryemulator8C.local where the mac address is 08:F9:E0:D1:06:8C
String mac = WiFi.macAddress();
String mdnsHost = "batteryemulator" + mac.substring(mac.length() - 2);
// Initialize mDNS .local resolution
if (!MDNS.begin(mdnsHost)) {
#ifdef DEBUG_LOG
logging.println("Error setting up MDNS responder!");
#endif
} else {
// Advertise via bonjour the service so we can auto discover these battery emulators on the local network.
MDNS.addService("battery_emulator", "tcp", 80);
}
}
#endif // MDNSRESPONDER
#ifdef WIFIAP
void init_WiFi_AP() {
#ifdef DEBUG_LOG
logging.println("Creating Access Point: " + String(ssidAP));
logging.println("With password: " + String(passwordAP));
#endif
WiFi.softAP(ssidAP, passwordAP);
IPAddress IP = WiFi.softAPIP();
#ifdef DEBUG_LOG
logging.println("Access Point created.");
logging.print("IP address: ");
logging.println(IP);
#endif
}
#endif // WIFIAP

View file

@ -0,0 +1,35 @@
#ifndef WIFI_H
#define WIFI_H
#include <WiFi.h>
#include <string>
#include "../../include.h"
#ifdef MDNSRESPONDER
#include <ESPmDNS.h>
#endif // MDNSRESONDER
extern std::string ssid;
extern std::string password;
extern const uint8_t wifi_channel;
extern const char* ssidAP;
extern const char* passwordAP;
void init_WiFi();
void wifi_monitor();
static void connectToWiFi();
static void FullReconnectToWiFi();
static void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info);
static void onWifiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info);
static void onWifiGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
#ifdef WIFIAP
void init_WiFi_AP();
#endif // WIFIAP
#ifdef MDNSRESPONDER
// Initialise mDNS
void init_mDNS();
#endif // MDNSRESPONDER
#endif

View file

@ -9,6 +9,7 @@
#include "devboard/hal/hal.h"
#include "devboard/safety/safety.h"
#include "devboard/utils/logging.h"
#include "devboard/utils/time_meas.h"
#include "devboard/utils/types.h"
@ -22,15 +23,15 @@
#error You must select a HW to run on!
#endif
#if defined(DUAL_CAN) && defined(CAN_FD)
#if defined(CAN_ADDON) && defined(CANFD_ADDON)
// Check that user did not try to use dual can and fd-can on same hardware pins
#error CAN-FD AND DUAL-CAN CANNOT BE USED SIMULTANEOUSLY
#error CAN_ADDON AND CANFD_ADDON CANNOT BE USED SIMULTANEOUSLY
#endif
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
#if !defined(CAN_FD)
#if !defined(CANFD_ADDON)
// Check that user did not try to use classic CAN over FD, without FD component
#error PLEASE ENABLE CAN_FD TO USE CLASSIC CAN OVER CANFD INTERFACE
#error PLEASE ENABLE CANFD_ADDON TO USE CLASSIC CAN OVER CANFD INTERFACE
#endif
#endif
@ -41,14 +42,15 @@
#endif
#endif
#ifdef RS485_INVERTER_SELECTED
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
// Check that Dual LilyGo via RS485 option isn't enabled, this collides with Modbus!
#error RS485 CANNOT BE USED IN DOUBLE LILYGO SETUPS! CHECK USER SETTINGS!
#endif
#endif
#ifndef BATTERY_SELECTED
#error No battery selected! Choose one from the USER_SETTINGS.h file
#endif
#ifdef KIA_E_GMP_BATTERY
#ifndef CAN_FD
#error KIA HYUNDAI EGMP BATTERIES CANNOT BE USED WITHOUT CAN FD
#endif
#endif
#endif

Some files were not shown because too many files have changed in this diff Show more