Merge branch 'main' into feature/double-automatic-contactor

This commit is contained in:
Daniel Öster 2024-11-23 23:59:28 +02:00
commit 9a7dda5e11
65 changed files with 1049 additions and 396 deletions

View file

@ -34,19 +34,25 @@ jobs:
# These are the batteries for which the code will be compiled. # These are the batteries for which the code will be compiled.
battery: battery:
- BMW_I3_BATTERY - BMW_I3_BATTERY
- BMW_IX_BATTERY
- BYD_ATTO_3_BATTERY - BYD_ATTO_3_BATTERY
- CELLPOWER_BMS - CELLPOWER_BMS
- CHADEMO_BATTERY - CHADEMO_BATTERY
- IMIEV_CZERO_ION_BATTERY - IMIEV_CZERO_ION_BATTERY
- JAGUAR_IPACE_BATTERY - JAGUAR_IPACE_BATTERY
- KIA_HYUNDAI_64_BATTERY - KIA_HYUNDAI_64_BATTERY
- KIA_E_GMP_BATTERY
- KIA_HYUNDAI_HYBRID_BATTERY - KIA_HYUNDAI_HYBRID_BATTERY
- MG_5_BATTERY
- NISSAN_LEAF_BATTERY - NISSAN_LEAF_BATTERY
- PYLON_BATTERY - PYLON_BATTERY
- RJXZS_BMS - RJXZS_BMS
- RANGE_ROVER_PHEV_BATTERY
- RENAULT_KANGOO_BATTERY - RENAULT_KANGOO_BATTERY
- RENAULT_TWIZY_BATTERY
- RENAULT_ZOE_GEN1_BATTERY - RENAULT_ZOE_GEN1_BATTERY
- RENAULT_ZOE_GEN2_BATTERY - RENAULT_ZOE_GEN2_BATTERY
- SANTA_FE_PHEV_BATTERY
- TESLA_MODEL_3Y_BATTERY - TESLA_MODEL_3Y_BATTERY
- VOLVO_SPA_BATTERY - VOLVO_SPA_BATTERY
- TEST_FAKE_BATTERY - TEST_FAKE_BATTERY
@ -54,13 +60,6 @@ jobs:
# These are the emulated inverter communication protocols for which the code will be compiled. # These are the emulated inverter communication protocols for which the code will be compiled.
inverter: inverter:
- BYD_CAN - BYD_CAN
# - BYD_MODBUS
# - PYLON_CAN
# - SMA_CAN
# - SMA_TRIPOWER_CAN
# - SOFAR_CAN
# - SOLAX_CAN
# This is the platform GitHub will use to run our workflow. # This is the platform GitHub will use to run our workflow.
runs-on: ubuntu-latest runs-on: ubuntu-latest

View file

@ -42,11 +42,14 @@ jobs:
# - TESLA_MODEL_3Y_BATTERY # - TESLA_MODEL_3Y_BATTERY
# These are the emulated inverter communication protocols for which the code will be compiled. # These are the emulated inverter communication protocols for which the code will be compiled.
inverter: inverter:
- AFORE_CAN
- BYD_CAN - BYD_CAN
- BYD_SMA - BYD_SMA
- BYD_MODBUS - BYD_MODBUS
- FOXESS_CAN - FOXESS_CAN
- PYLON_LV_CAN
- PYLON_CAN - PYLON_CAN
- SCHNEIDER_CAN
- SMA_CAN - SMA_CAN
- SMA_TRIPOWER_CAN - SMA_TRIPOWER_CAN
- SOFAR_CAN - SOFAR_CAN

View file

@ -53,7 +53,7 @@
Preferences settings; // Store user settings Preferences settings; // Store user settings
// The current software version, shown on webserver // The current software version, shown on webserver
const char* version_number = "7.7.dev"; const char* version_number = "7.8.dev";
// Interval settings // Interval settings
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
@ -70,13 +70,11 @@ volatile bool send_ok = 0;
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT); ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT);
static ACAN2515_Buffer16 gBuffer; static ACAN2515_Buffer16 gBuffer;
#endif #endif //DUAL_CAN
#ifdef CAN_FD #ifdef CAN_FD
#include "src/lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h" #include "src/lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT); ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT);
#else #endif //CAN_FD
typedef char CANFDMessage;
#endif
// ModbusRTU parameters // ModbusRTU parameters
#ifdef MODBUS_INVERTER_SELECTED #ifdef MODBUS_INVERTER_SELECTED
@ -194,11 +192,10 @@ void setup() {
init_rs485(); init_rs485();
init_serialDataLink(); init_serialDataLink();
#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED)
init_inverter(); setup_inverter();
#endif
init_battery(); setup_battery();
#ifdef EQUIPMENT_STOP_BUTTON #ifdef EQUIPMENT_STOP_BUTTON
init_equipment_stop_button(); init_equipment_stop_button();
#endif #endif
@ -581,29 +578,6 @@ void init_rs485() {
#endif #endif
} }
void init_inverter() {
#ifdef SOLAX_CAN
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
intervalUpdateValues = 800; // This protocol also requires the values to be updated faster
#endif
#ifdef FOXESS_CAN
intervalUpdateValues = 950; // This protocol also requires the values to be updated faster
#endif
#ifdef BYD_SMA
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT);
#endif
}
void init_battery() {
// Inform user what battery is used and perform setup
setup_battery();
#ifdef CHADEMO_BATTERY
intervalUpdateValues = 800; // This mode requires the values to be updated faster
#endif
}
#ifdef EQUIPMENT_STOP_BUTTON #ifdef EQUIPMENT_STOP_BUTTON
void monitor_equipment_stop_button() { void monitor_equipment_stop_button() {
@ -1175,6 +1149,9 @@ void receive_can(CAN_frame* rx_frame, int interface) {
if (interface == can_config.battery) { if (interface == can_config.battery) {
receive_can_battery(*rx_frame); receive_can_battery(*rx_frame);
#ifdef CHADEMO_BATTERY
ISA_handleFrame(rx_frame);
#endif
} }
if (interface == can_config.inverter) { if (interface == can_config.inverter) {
#ifdef CAN_INVERTER_SELECTED #ifdef CAN_INVERTER_SELECTED

View file

@ -43,6 +43,7 @@
//#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus //#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus
//#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus //#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus
//#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus //#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus
//#define SCHNEIDER_CAN //Enable this line to emulate a "Schneider Version 2: SE BMS" over CAN bus
//#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus
//#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus //#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus
//#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus //#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus

View file

@ -20,6 +20,7 @@
#ifdef CHADEMO_BATTERY #ifdef CHADEMO_BATTERY
#include "CHADEMO-BATTERY.h" #include "CHADEMO-BATTERY.h"
#include "CHADEMO-SHUNTS.h"
#endif #endif
#ifdef IMIEV_CZERO_ION_BATTERY #ifdef IMIEV_CZERO_ION_BATTERY

View file

@ -1118,9 +1118,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "BMW i3", 63);
Serial.println("BMW i3 battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif //DEBUG_VIA_USB
//Before we have started up and detected which battery is in use, use 60AH values //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.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
@ -1129,9 +1128,6 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
#ifdef DOUBLE_BATTERY #ifdef DOUBLE_BATTERY
#ifdef DEBUG_VIA_USB
Serial.println("Another BMW i3 battery also selected!");
#endif //DEBUG_VIA_USB
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; 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.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.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;

View file

@ -457,17 +457,18 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.temperature_max_dC = max_battery_temperature; datalayer.battery.status.temperature_max_dC = max_battery_temperature;
if (isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged)) { //Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze.
datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop bool isMinCellVoltageStale =
set_event(EVENT_CAN_RX_FAILURE, 0); isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged);
} else { bool isMaxCellVoltageStale =
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged);
}
if (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 datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop
set_event(EVENT_CAN_RX_FAILURE, 0); set_event(EVENT_CAN_RX_FAILURE, 0);
} else { } 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.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive
} }
@ -672,7 +673,7 @@ void receive_can_battery(CAN_frame rx_frame) {
#ifdef DEBUG_VIA_USB #ifdef DEBUG_VIA_USB
Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset"); Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset");
#endif #endif
set_event(EVENT_SOC_UNAVAILABLE, (millis())); //set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type
transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery); transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
} else { //Only ingest values if they are not the 10V Error state } 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]); min_cell_voltage = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
@ -778,9 +779,8 @@ void send_can_battery() {
//} //We can always send CAN as the iX BMS will wake up on vehicle comms //} //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 void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", 63);
Serial.println("BMW iX battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif //DEBUG_VIA_USB
//Before we have started up and detected which battery is in use, use 108S values //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.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -15,6 +15,7 @@
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send 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 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 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_50ms = 0;
static uint8_t counter_100ms = 0; static uint8_t counter_100ms = 0;
static uint8_t frame6_counter = 0xB; static uint8_t frame6_counter = 0xB;
@ -61,6 +62,8 @@ static uint16_t BMS2_highest_cell_voltage_mV = 3300;
#define POLL_FOR_BATTERY_CELL_MV_MAX 0x2D #define POLL_FOR_BATTERY_CELL_MV_MAX 0x2D
#define POLL_FOR_BATTERY_CELL_MV_MIN 0x2B #define POLL_FOR_BATTERY_CELL_MV_MIN 0x2B
#define UNKNOWN_POLL_1 0xFC #define UNKNOWN_POLL_1 0xFC
#define ESTIMATED 0
#define MEASURED 1
static uint16_t poll_state = POLL_FOR_BATTERY_SOC; static uint16_t poll_state = POLL_FOR_BATTERY_SOC;
CAN_frame ATTO_3_12D = {.FD = false, CAN_frame ATTO_3_12D = {.FD = false,
@ -108,19 +111,25 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.voltage_dV = BMS_voltage * 10; datalayer.battery.status.voltage_dV = BMS_voltage * 10;
} }
//datalayer.battery.status.real_soc = BMS_SOC * 100; //TODO: This is not yet found! #ifdef USE_ESTIMATED_SOC
// We instead estimate the SOC% based on the battery voltage // When the battery is crashed hard, it locks itself and SOC becomes unavailable.
// This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that! // 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); 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 * 100;
SOC_method = MEASURED;
#endif
datalayer.battery.status.current_dA = -BMS_current; datalayer.battery.status.current_dA = -BMS_current;
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>( 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); (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.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_max_voltage_mV = BMS_highest_cell_voltage_mV;
@ -144,6 +153,7 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10; datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10;
// Update webserver datalayer // Update webserver datalayer
datalayer_extended.bydAtto3.SOC_method = SOC_method;
datalayer_extended.bydAtto3.SOC_estimated = datalayer.battery.status.real_soc; datalayer_extended.bydAtto3.SOC_estimated = datalayer.battery.status.real_soc;
//Once we implement switching logic, remember to change from where the estimated is taken //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_highprec = battery_highprecision_SOC;
@ -228,6 +238,7 @@ void receive_can_battery(CAN_frame rx_frame) {
case 0x444: //9E,01,88,13,64,64,98,65 case 0x444: //9E,01,88,13,64,64,98,65
//9A,01,B6,13,64,64,98,3B //407.5V 18deg //9A,01,B6,13,64,64,98,3B //407.5V 18deg
//9B,01,B8,13,64,64,98,38 //408.5V 14deg //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_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 //battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7
break; break;
@ -399,9 +410,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63);
Serial.println("BYD Atto 3 battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.number_of_cells = 126; datalayer.battery.info.number_of_cells = 126;
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -3,6 +3,12 @@
#include "../include.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 BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4410 //5000 = 500.0V #define MAX_PACK_VOLTAGE_DV 4410 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 3800 #define MIN_PACK_VOLTAGE_DV 3800

View file

@ -333,9 +333,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Cellpower BMS", 63);
Serial.println("Cellpower BMS selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -120,7 +120,7 @@ void update_values_battery() {
datalayer.battery.status.voltage_dV = get_measured_voltage() * 10; datalayer.battery.status.voltage_dV = get_measured_voltage() * 10;
datalayer.battery.info.total_capacity_Wh = 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? 1000); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
/* TODO max charging rate = /* TODO max charging rate =
@ -151,8 +151,8 @@ void update_values_battery() {
inline void process_vehicle_charging_minimums(CAN_frame rx_frame) { inline void process_vehicle_charging_minimums(CAN_frame rx_frame) {
x100_chg_lim.MinimumChargeCurrent = rx_frame.data.u8[0]; 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.MinimumBatteryVoltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]); x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
x100_chg_lim.ConstantOfChargingRateIndication = rx_frame.data.u8[6]; 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.MaxChargingTime10sBit = rx_frame.data.u8[1];
x101_chg_est.MaxChargingTime1minBit = rx_frame.data.u8[2]; x101_chg_est.MaxChargingTime1minBit = rx_frame.data.u8[2];
x101_chg_est.EstimatedChargingTime = rx_frame.data.u8[3]; 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) { inline void process_vehicle_charging_session(CAN_frame rx_frame) {
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]); uint16_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
uint16_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
uint8_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
uint8_t newChargingCurrentRequest = rx_frame.data.u8[3]; uint8_t newChargingCurrentRequest = rx_frame.data.u8[3];
uint8_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
vehicle_can_initialized = true; 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.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.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.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]; x102_chg_session.StateOfCharge = rx_frame.data.u8[6];
@ -202,7 +202,7 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
uint8_t chargingrate = 0; uint8_t chargingrate = 0;
if (x100_chg_lim.ConstantOfChargingRateIndication > 0) { if (x100_chg_lim.ConstantOfChargingRateIndication > 0) {
chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100; chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
Serial.print("Charge Rate (kW):"); Serial.print("Charge Rate (kW): ");
Serial.println(chargingrate); Serial.println(chargingrate);
} }
#endif #endif
@ -308,7 +308,7 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
inline void process_vehicle_charging_limits(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.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.MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7]; x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
@ -338,15 +338,15 @@ inline void process_vehicle_discharge_estimate(CAN_frame rx_frame) {
unsigned long currentMillis = millis(); unsigned long currentMillis = millis();
x201_discharge_estimate.V2HchargeDischargeSequenceNum = rx_frame.data.u8[0]; 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.ApproxDischargeCompletionTime = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]); x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[3]);
#ifdef DEBUG_VIA_USB #ifdef DEBUG_VIA_USB
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) { if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
previousMillis5000 = currentMillis; previousMillis5000 = currentMillis;
Serial.println("x201 availabile vehicle energy, completion time"); Serial.print("x201 availabile vehicle energy, completion time: ");
Serial.println(x201_discharge_estimate.AvailableVehicleEnergy); Serial.println(x201_discharge_estimate.AvailableVehicleEnergy);
Serial.println("x201 approx vehicle completion time"); Serial.print("x201 approx vehicle completion time: ");
Serial.println(x201_discharge_estimate.ApproxDischargeCompletionTime); Serial.println(x201_discharge_estimate.ApproxDischargeCompletionTime);
} }
#endif #endif
@ -364,7 +364,7 @@ inline void process_vehicle_dynamic_control(CAN_frame rx_frame) {
inline void process_vehicle_vendor_ID(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.AutomakerCode = rx_frame.data.u8[0];
x700_vendor_id.OptionalContent = 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) { void receive_can_battery(CAN_frame rx_frame) {
@ -557,7 +557,7 @@ void update_evse_status(CAN_frame& f) {
* *
*/ */
if ((x102_chg_session.TargetBatteryVoltage > x108_evse_cap.available_output_voltage) || 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 //Toggle battery incompatibility flag 109.5.3
x109_evse_state.s.status.EVSE_error = 1; x109_evse_state.s.status.EVSE_error = 1;
x109_evse_state.s.status.battery_incompatible = 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[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 */ /* x208 EVSE, peer to 0x200 Vehicle */
@ -751,7 +752,7 @@ void handle_chademo_sequence() {
/* ------------------- State override conditions checks ------------------- */ /* ------------------- State override conditions checks ------------------- */
/* ------------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------------ */
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !x102_chg_session.s.status.StatusVehicleShifterPosition) { if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && x102_chg_session.s.status.StatusVehicleShifterPosition) {
#ifdef DEBUG_VIA_USB #ifdef DEBUG_VIA_USB
Serial.println("Vehicle is not parked, abort."); Serial.println("Vehicle is not parked, abort.");
#endif #endif
@ -777,7 +778,6 @@ void handle_chademo_sequence() {
#ifdef DEBUG_VIA_USB #ifdef DEBUG_VIA_USB
// Commented unless needed for debug // Commented unless needed for debug
// Serial.println("CHADEMO plug is not inserted."); // Serial.println("CHADEMO plug is not inserted.");
//
#endif #endif
return; return;
} }
@ -1031,9 +1031,18 @@ void handle_chademo_sequence() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Chademo battery selected"); pinMode(CHADEMO_PIN_2, OUTPUT);
#endif 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; 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; x109_evse_state.s.status.ChgDischStopControl = 1;
handle_chademo_sequence(); 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(); setupMillis = millis();
} }

View file

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

View file

@ -5,7 +5,7 @@
uint16_t get_measured_voltage(); uint16_t get_measured_voltage();
uint16_t get_measured_current(); 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_handle521(CAN_frame* frame);
inline void ISA_handle522(CAN_frame* frame); inline void ISA_handle522(CAN_frame* frame);
inline void ISA_handle523(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_handle526(CAN_frame* frame);
inline void ISA_handle527(CAN_frame* frame); inline void ISA_handle527(CAN_frame* frame);
inline void ISA_handle528(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); void transmit_can(CAN_frame* tx_frame, int interface);

View file

@ -224,9 +224,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "I-Miev / C-Zero / Ion Triplet", 63);
Serial.println("Mitsubishi i-MiEV / Citroen C-Zero / Peugeot Ion battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;

View file

@ -254,10 +254,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Jaguar I-PACE", 63);
Serial.println("Jaguar iPace 90kWh battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.number_of_cells = 108;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -1037,14 +1037,12 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", 63);
Serial.println("Hyundai E-GMP (Electric Global Modular Platform) battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
startMillis = millis(); // Record the starting time startMillis = millis(); // Record the starting time
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 192; // TODO: will vary depending on battery datalayer.battery.info.number_of_cells = 192; // TODO: will vary depending on battery
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -534,9 +534,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", 63);
Serial.println("Kia Niro / Hyundai Kona 64kWh battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; //Start with 98S value. Precised later 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.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.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;

View file

@ -257,9 +257,9 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai Hybrid", 63);
Serial.println("Kia/Hyundai Hybrid battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.number_of_cells = 56; // HEV , TODO: Make dynamic according to HEV/PHEV 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.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -135,9 +135,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", 63);
Serial.println("MG 5 battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -174,11 +174,17 @@ static int16_t battery2_temp_polled_max = 0;
static int16_t battery2_temp_polled_min = 0; static int16_t battery2_temp_polled_min = 0;
#endif // DOUBLE_BATTERY #endif // DOUBLE_BATTERY
void print_with_units(char* header, int value, char* units) { // Clear SOH values
Serial.print(header); static uint8_t stateMachineClearSOH = 0xFF;
Serial.print(value); static uint32_t incomingChallenge = 0xFFFFFFFF;
Serial.print(units); 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 */ 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 */ /* Start with mapping all values */
@ -334,17 +340,18 @@ void update_values_battery() { /* This function maps all the values fetched via
datalayer_extended.nissanleaf.HeatingStop = battery_Heating_Stop; datalayer_extended.nissanleaf.HeatingStop = battery_Heating_Stop;
datalayer_extended.nissanleaf.HeatingStart = battery_Heating_Start; datalayer_extended.nissanleaf.HeatingStart = battery_Heating_Start;
datalayer_extended.nissanleaf.HeaterSendRequest = battery_Batt_Heater_Mail_Send_Request; 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;
/*Finally print out values to serial if configured to do so*/ // Update requests from webserver datalayer
#ifdef DEBUG_VIA_USB if (datalayer_extended.nissanleaf.UserRequestSOHreset) {
Serial.println("Values from battery"); stateMachineClearSOH = 0; //Start the statemachine
print_with_units("Real SOC%: ", (battery_SOC * 0.1), "% "); datalayer_extended.nissanleaf.UserRequestSOHreset = false;
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
} }
#ifdef DOUBLE_BATTERY #ifdef DOUBLE_BATTERY
@ -599,7 +606,7 @@ void receive_can_battery2(CAN_frame rx_frame) {
} }
} }
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; break;
} }
@ -836,6 +843,22 @@ void receive_can_battery(CAN_frame rx_frame) {
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
break; break;
case 0x7BB: case 0x7BB:
// This section checks if we are doing a SOH reset towards BMS
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;
}
//First check which group data we are getting //First check which group data we are getting
if (rx_frame.data.u8[0] == 0x10) { //First message of a group if (rx_frame.data.u8[0] == 0x10) { //First message of a group
group_7bb = rx_frame.data.u8[3]; group_7bb = rx_frame.data.u8[3];
@ -1116,6 +1139,10 @@ void send_can_battery() {
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) { if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
previousMillis100 = currentMillis; previousMillis100 = currentMillis;
if (stateMachineClearSOH < 255) { // Enter the ClearSOH statemachine only if we request it
clearSOH();
}
//When battery requests heating pack status change, ack this //When battery requests heating pack status change, ack this
if (battery_Batt_Heater_Mail_Send_Request) { if (battery_Batt_Heater_Mail_Send_Request) {
LEAF_50B.data.u8[6] = 0x20; //Batt_Heater_Mail_Send_OK LEAF_50B.data.u8[6] = 0x20; //Batt_Heater_Mail_Send_OK
@ -1219,10 +1246,189 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib
return static_cast<uint16_t>(1094 + (309 - temperature) * 2.5714285714285715); 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 void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", 63);
Serial.println("Nissan LEAF battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -14,5 +14,13 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature);
bool is_message_corrupt(CAN_frame rx_frame); bool is_message_corrupt(CAN_frame rx_frame);
void setup_battery(void); void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface); 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 #endif

View file

@ -175,10 +175,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63);
Serial.println("Pylon battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;

View file

@ -313,10 +313,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Range Rover 13kWh PHEV battery (L494/L405)", 63);
Serial.println("Range Rover PHEV battery (L494 / L405) selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif //DEBUG_VIA_USB
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;

View file

@ -234,9 +234,9 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Renault Kangoo battery selected"); strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", 63);
#endif datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -132,10 +132,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", 63);
Serial.println("Renault Twizy battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.number_of_cells = 14; datalayer.battery.info.number_of_cells = 14;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -518,9 +518,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen1 22/40kWh", 63);
Serial.println("Renault Zoe 22/40kWh battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -385,9 +385,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", 63);
Serial.println("Renault Zoe 50kWh battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -570,10 +570,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", 63);
Serial.println("RJXZS BMS selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;

View file

@ -402,9 +402,8 @@ uint8_t CalculateCRC8(CAN_frame rx_frame) {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", 63);
Serial.println("Hyundai Santa Fe PHEV battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.number_of_cells = 96; datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -219,7 +219,8 @@ void update_values_serial_link() {
} }
void setup_battery(void) { 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 // Needed to make the compiler happy
void update_values_battery() {} void update_values_battery() {}

View file

@ -1249,13 +1249,11 @@ void printDebugIfActive(uint8_t symbol, const char* message) {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Tesla Model S/3/X/Y battery selected");
#endif
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
#ifdef TESLA_MODEL_SX_BATTERY // Always use NCM/A mode on S/X packs #ifdef TESLA_MODEL_SX_BATTERY // Always use NCM/A mode on S/X packs
strncpy(datalayer.system.info.battery_protocol, "Tesla Model S/X", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_SX_NCMA; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_SX_NCMA;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA; datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM; datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM;
@ -1271,6 +1269,8 @@ void setup_battery(void) { // Performs one time setup at startup
#endif // TESLA_MODEL_SX_BATTERY #endif // TESLA_MODEL_SX_BATTERY
#ifdef TESLA_MODEL_3Y_BATTERY // Model 3/Y can be either LFP or NCM/A #ifdef TESLA_MODEL_3Y_BATTERY // Model 3/Y can be either LFP or NCM/A
strncpy(datalayer.system.info.battery_protocol, "Tesla Model 3/Y", 63);
datalayer.system.info.battery_protocol[63] = '\0';
#ifdef LFP_CHEMISTRY #ifdef LFP_CHEMISTRY
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP; datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP; datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP;

View file

@ -145,9 +145,8 @@ void send_can_battery() {
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
randomSeed(analogRead(0)); randomSeed(analogRead(0));
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", 63);
Serial.println("Test mode with fake battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV =
4040; // 404.4V, over this, charging is not possible (goes into forced discharge) 4040; // 404.4V, over this, charging is not possible (goes into forced discharge)

View file

@ -332,10 +332,8 @@ void send_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 78kWh battery", 63);
Serial.println("Volvo SPA XC40 Recharge / Polestar2 78kWh battery selected"); datalayer.system.info.battery_protocol[63] = '\0';
#endif
datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.number_of_cells = 108;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -127,7 +127,10 @@ typedef struct {
} DATALAYER_SHUNT_TYPE; } DATALAYER_SHUNT_TYPE;
typedef struct { 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};
} DATALAYER_SYSTEM_INFO_TYPE; } DATALAYER_SYSTEM_INFO_TYPE;
typedef struct { typedef struct {

View file

@ -71,6 +71,9 @@ typedef struct {
} DATALAYER_INFO_BMWI3; } DATALAYER_INFO_BMWI3;
typedef struct { typedef struct {
/** bool */
/** Which SOC method currently used. 0 = Estimated, 1 = Measured */
bool SOC_method = 0;
/** uint16_t */ /** uint16_t */
/** SOC% estimate. Estimated from total pack voltage */ /** SOC% estimate. Estimated from total pack voltage */
uint16_t SOC_estimated = 0; uint16_t SOC_estimated = 0;
@ -223,6 +226,21 @@ typedef struct {
/** bool */ /** bool */
/** Heat request sent*/ /** Heat request sent*/
bool HeaterSendRequest = false; 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; } DATALAYER_INFO_NISSAN_LEAF;

View file

@ -275,6 +275,8 @@ String advanced_battery_processor(const String& var) {
#endif //CELLPOWER_BMS #endif //CELLPOWER_BMS
#ifdef BYD_ATTO_3_BATTERY #ifdef BYD_ATTO_3_BATTERY
static const char* SOCmethod[2] = {"Estimated from voltage", "Measured by BMS"};
content += "<h4>SOC method used: " + String(SOCmethod[datalayer_extended.bydAtto3.SOC_method]) + "</h4>";
content += "<h4>SOC estimated: " + String(datalayer_extended.bydAtto3.SOC_estimated) + "</h4>"; content += "<h4>SOC estimated: " + String(datalayer_extended.bydAtto3.SOC_estimated) + "</h4>";
content += "<h4>SOC highprec: " + String(datalayer_extended.bydAtto3.SOC_highprec) + "</h4>"; content += "<h4>SOC highprec: " + String(datalayer_extended.bydAtto3.SOC_highprec) + "</h4>";
content += "<h4>SOC OBD2: " + String(datalayer_extended.bydAtto3.SOC_polled) + "</h4>"; content += "<h4>SOC OBD2: " + String(datalayer_extended.bydAtto3.SOC_polled) + "</h4>";
@ -331,6 +333,11 @@ String advanced_battery_processor(const String& var) {
content += "<h4>Heating stopped: " + String(datalayer_extended.nissanleaf.HeatingStop) + "</h4>"; content += "<h4>Heating stopped: " + String(datalayer_extended.nissanleaf.HeatingStop) + "</h4>";
content += "<h4>Heating started: " + String(datalayer_extended.nissanleaf.HeatingStart) + "</h4>"; content += "<h4>Heating started: " + String(datalayer_extended.nissanleaf.HeatingStart) + "</h4>";
content += "<h4>Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "</h4>"; content += "<h4>Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "</h4>";
content += "<button onclick='askResetSOH()'>Reset degradation data</button>";
content += "<h4>CryptoChallenge: " + String(datalayer_extended.nissanleaf.CryptoChallenge) + "</h4>";
content += "<h4>SolvedChallenge: " + String(datalayer_extended.nissanleaf.SolvedChallengeMSB) +
String(datalayer_extended.nissanleaf.SolvedChallengeLSB) + "</h4>";
content += "<h4>Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "</h4>";
#endif #endif
#ifdef RENAULT_ZOE_GEN2_BATTERY #ifdef RENAULT_ZOE_GEN2_BATTERY
@ -388,6 +395,15 @@ String advanced_battery_processor(const String& var) {
content += "</div>"; content += "</div>";
content += "<script>"; content += "<script>";
content +=
"function askResetSOH() { if (window.confirm('Are you sure you want to reset degradation data? "
"Note this should only be used on 2011-2017 24/30kWh batteries!')) { "
"resetSOH(); } }";
content += "function resetSOH() {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/resetSOH', true);";
content += " xhr.send();";
content += "}";
content += "function goToMainPage() { window.location.href = '/'; }"; content += "function goToMainPage() { window.location.href = '/'; }";
content += "</script>"; content += "</script>";
return content; return content;

View file

@ -1,6 +1,7 @@
#include "webserver.h" #include "webserver.h"
#include <Preferences.h> #include <Preferences.h>
#include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer.h"
#include "../../datalayer/datalayer_extended.h"
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h" #include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
#include "../utils/events.h" #include "../utils/events.h"
#include "../utils/led_handler.h" #include "../utils/led_handler.h"
@ -231,6 +232,15 @@ void init_webserver() {
} }
}); });
// 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 #ifdef TEST_FAKE_BATTERY
// Route for editing FakeBatteryVoltage // Route for editing FakeBatteryVoltage
server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) { server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
@ -486,117 +496,16 @@ String processor(const String& var) {
// Display which components are used // Display which components are used
content += "<h4 style='color: white;'>Inverter protocol: "; content += "<h4 style='color: white;'>Inverter protocol: ";
#ifdef BYD_CAN content += datalayer.system.info.inverter_protocol;
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 FOXESS_CAN
content += "FoxESS compatible HV2600/ECS4100 battery";
#endif // FOXESS_CAN
#ifdef PYLON_CAN
content += "Pylontech battery over CAN bus";
#endif // PYLON_CAN
#ifdef PYLON_LV_CAN
content += "Pylontech LV battery over CAN bus";
#endif // PYLON_LV_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 += "</h4>"; content += "</h4>";
content += "<h4 style='color: white;'>Battery protocol: "; content += "<h4 style='color: white;'>Battery protocol: ";
#ifdef BMW_I3_BATTERY content += datalayer.system.info.battery_protocol;
content += "BMW i3";
#endif // BMW_I3_BATTERY
#ifdef BMW_IX_BATTERY
content += "BMW iX and i4-7 platform";
#endif // BMW_IX_BATTERY
#ifdef BYD_ATTO_3_BATTERY
content += "BYD Atto 3";
#endif // BYD_ATTO_3_BATTERY
#ifdef CELLPOWER_BMS
content += "Cellpower BMS";
#endif // CELLPOWER_BMS
#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 PYLON_BATTERY
content += "Pylon compatible battery";
#endif // PYLON_BATTERY
#ifdef RJXZS_BMS
content += "RJXZS BMS, DIY battery";
#endif // RJXZS_BMS
#ifdef RANGE_ROVER_PHEV_BATTERY
content += "Range Rover 13kWh PHEV battery (L494/L405)";
#endif //RANGE_ROVER_PHEV_BATTERY
#ifdef RENAULT_KANGOO_BATTERY
content += "Renault Kangoo";
#endif // RENAULT_KANGOO_BATTERY
#ifdef RENAULT_TWIZY_BATTERY
content += "Renault Twizy";
#endif // RENAULT_TWIZY_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
#ifdef DOUBLE_BATTERY #ifdef DOUBLE_BATTERY
content += " (Double battery)"; content += " (Double battery)";
#endif // DOUBLE_BATTERY
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) { if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
content += " (LFP)"; content += " (LFP)";
} }
#endif // DOUBLE_BATTERY
content += "</h4>"; content += "</h4>";
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER #if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
@ -676,16 +585,16 @@ String processor(const String& var) {
content += content +=
formatPowerValue("Scaled Remaining capacity", datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1); formatPowerValue("Scaled Remaining capacity", datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1);
if (emulator_pause_status == NORMAL) { if (datalayer.system.settings.equipment_stop_active) {
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>";
} else {
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red"); 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 += 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 discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 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 max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
@ -805,8 +714,19 @@ String processor(const String& var) {
content += formatPowerValue("Real Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1); content += formatPowerValue("Real Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
content += content +=
formatPowerValue("Scaled Remaining capacity", datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1); formatPowerValue("Scaled Remaining capacity", datalayer.battery2.status.reported_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); 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 max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
content += "<h4>Cell min: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV</h4>"; content += "<h4>Cell min: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV</h4>";
if (cell_delta_mv > datalayer.battery2.info.max_cell_voltage_deviation_mV) { if (cell_delta_mv > datalayer.battery2.info.max_cell_voltage_deviation_mV) {

View file

@ -45,10 +45,4 @@
#error No battery selected! Choose one from the USER_SETTINGS.h file #error No battery selected! Choose one from the USER_SETTINGS.h file
#endif #endif
#ifdef KIA_E_GMP_BATTERY
#ifndef CAN_FD
#error KIA HYUNDAI EGMP BATTERIES CANNOT BE USED WITHOUT CAN FD
#endif
#endif
#endif #endif

View file

@ -233,4 +233,8 @@ void send_can_inverter() {
time_to_send_info = false; time_to_send_info = false;
} }
} }
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Afore battery over CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif #endif

View file

@ -4,8 +4,7 @@
#define CAN_INVERTER_SELECTED #define CAN_INVERTER_SELECTED
void send_system_data();
void send_setup_info();
void transmit_can(CAN_frame* tx_frame, int interface); void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif #endif

View file

@ -76,6 +76,8 @@ static uint8_t inverter_name[7] = {0};
static int16_t temperature_average = 0; static int16_t temperature_average = 0;
static uint16_t inverter_voltage = 0; static uint16_t inverter_voltage = 0;
static uint16_t inverter_SOC = 0; static uint16_t inverter_SOC = 0;
static int16_t inverter_current = 0;
static int16_t inverter_temperature = 0;
static uint16_t remaining_capacity_ah = 0; static uint16_t remaining_capacity_ah = 0;
static uint16_t fully_charged_capacity_ah = 0; static uint16_t fully_charged_capacity_ah = 0;
static long inverter_timestamp = 0; static long inverter_timestamp = 0;
@ -165,6 +167,8 @@ void receive_can_inverter(CAN_frame rx_frame) {
case 0x091: case 0x091:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
inverter_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1; inverter_voltage = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1;
inverter_current = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]) * 0.1;
inverter_temperature = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]) * 0.1;
break; break;
case 0x0D1: case 0x0D1:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE; datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
@ -219,4 +223,8 @@ void send_intial_data() {
transmit_can(&BYD_3D0_2, can_config.inverter); transmit_can(&BYD_3D0_2, can_config.inverter);
transmit_can(&BYD_3D0_3, can_config.inverter); transmit_can(&BYD_3D0_3, can_config.inverter);
} }
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box Premium HVS over CAN Bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif #endif

View file

@ -8,5 +8,6 @@
void send_intial_data(); void send_intial_data();
void transmit_can(CAN_frame* tx_frame, int interface); void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif #endif

View file

@ -143,4 +143,8 @@ void verify_inverter_modbus() {
history_index = (history_index + 1) % HISTORY_LENGTH; history_index = (history_index + 1) % HISTORY_LENGTH;
} }
} }
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "BYD 11kWh HVM battery over Modbus RTU", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif #endif

View file

@ -14,4 +14,5 @@ void verify_temperature_modbus();
void verify_inverter_modbus(); void verify_inverter_modbus();
void handle_update_data_modbusp201_byd(); void handle_update_data_modbusp201_byd();
void handle_update_data_modbusp301_byd(); void handle_update_data_modbusp301_byd();
void setup_inverter(void);
#endif #endif

View file

@ -251,4 +251,10 @@ void send_can_inverter() {
} }
} }
} }
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "BYD Battery-Box HVS over SMA CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT);
}
#endif #endif

View file

@ -8,5 +8,6 @@
#define STOP_STATE 0x02 #define STOP_STATE 0x02
void transmit_can(CAN_frame* tx_frame, int interface); void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif #endif

View file

@ -737,4 +737,8 @@ void receive_can_inverter(CAN_frame rx_frame) {
} }
} }
} }
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "FoxESS compatible HV2600/ECS4100 battery", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif #endif

View file

@ -5,5 +5,6 @@
#define CAN_INVERTER_SELECTED #define CAN_INVERTER_SELECTED
void transmit_can(CAN_frame* tx_frame, int interface); void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif #endif

View file

@ -31,6 +31,10 @@
#include "PYLON-LV-CAN.h" #include "PYLON-LV-CAN.h"
#endif #endif
#ifdef SCHNEIDER_CAN
#include "SCHNEIDER-CAN.h"
#endif
#ifdef SMA_CAN #ifdef SMA_CAN
#include "SMA-CAN.h" #include "SMA-CAN.h"
#endif #endif

View file

@ -477,4 +477,8 @@ void send_system_data() { //System equipment information
transmit_can(&PYLON_4291, can_config.inverter); transmit_can(&PYLON_4291, can_config.inverter);
#endif #endif
} }
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Pylontech battery over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif #endif

View file

@ -7,5 +7,6 @@
void send_system_data(); void send_system_data();
void send_setup_info(); void send_setup_info();
void transmit_can(CAN_frame* tx_frame, int interface); void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif #endif

View file

@ -133,4 +133,8 @@ void send_can_inverter() {
transmit_can(&PYLON_35E, can_config.inverter); transmit_can(&PYLON_35E, can_config.inverter);
} }
} }
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Pylontech LV battery over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif #endif

View file

@ -12,5 +12,6 @@
void send_system_data(); void send_system_data();
void send_setup_info(); void send_setup_info();
void transmit_can(CAN_frame* tx_frame, int interface); void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif #endif

View file

@ -0,0 +1,305 @@
#include "../include.h"
#ifdef SCHNEIDER_CAN
#include "../datalayer/datalayer.h"
#include "SCHNEIDER-CAN.h"
/* Version 2: SE BMS Communication Protocol
Protocol: CAN 2.0 Specification
Frame: Extended CAN Bus Frame (29 bit identifier)
Bitrate: 500 kbps
Endian: Big Endian (MSB, most significant byte of a value received first)*/
/* TODOs
- Figure out how to reply with protocol version in 0x320
- Figure out what to set Battery Manufacturer ID to in 0x330
- Figure out what to set Battery Model ID in 0x330
- We will need CAN logs from existing battery OR contact Schneider for one free number
*/
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
CAN_frame SE_320 = {.FD = false, //SE BMS Protocol Version
.ext_ID = true,
.DLC = 2,
.ID = 0x320,
.data = {0x00, 0x02}}; //TODO: How do we reply with Protocol Version: 0x0002 ?
CAN_frame SE_321 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x321,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_322 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x322,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_323 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x323,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_324 = {.FD = false, .ext_ID = true, .DLC = 4, .ID = 0x324, .data = {0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_325 = {.FD = false, .ext_ID = true, .DLC = 6, .ID = 0x325, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_326 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x326,
.data = {0x00, STATE_STARTING, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_327 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x327,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_328 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x328,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_330 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x330,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_331 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x331,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_332 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x332,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame SE_333 = {.FD = false,
.ext_ID = true,
.DLC = 8,
.ID = 0x333,
.data = {0x53, 0x45, 0x42, 0x4D, 0x53, 0x00, 0x00, 0x00}}; //SEBMS
static int16_t temperature_average = 0;
static uint16_t remaining_capacity_ah = 0;
static uint16_t fully_charged_capacity_ah = 0;
static uint16_t commands = 0;
static uint16_t warnings = 0;
static uint16_t faults = 0;
static uint16_t state = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
/* Calculate temperature */
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
/* Calculate capacity, Amp hours(Ah) = Watt hours (Wh) / Voltage (V)*/
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
remaining_capacity_ah =
((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * 100);
fully_charged_capacity_ah =
((datalayer.battery.info.total_capacity_Wh / datalayer.battery.status.voltage_dV) * 100);
}
/* Set active commands/warnings/faults/state*/
if (datalayer.battery.status.bms_status == FAULT) {
state = STATE_FAULTED;
//TODO: Map warnings and faults incase an event is set. Low prio, but nice to have
commands = COMMAND_STOP;
} else { //Battery-Emulator running
state = STATE_ONLINE;
warnings = 0;
faults = 0;
if (datalayer.battery.status.reported_soc == 10000) {
//Battery full. Only allow discharge
commands = COMMAND_ONLY_DISCHARGE_ALLOWED;
} else if (datalayer.battery.status.reported_soc == 0) {
//Battery empty. Only allow charge
commands = COMMAND_ONLY_CHARGE_ALLOWED;
} else { //SOC is somewhere between 0.1% and 99.9%. Allow both charge and discharge
commands = COMMAND_CHARGE_AND_DISCHARGE_ALLOWED;
}
}
//Map values to CAN messages
//Max charge voltage+2 (eg 10000.00V = 1000000 , 32bits long)
SE_321.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV * 10) >> 24);
SE_321.data.u8[1] = (((datalayer.battery.info.max_design_voltage_dV * 10) & 0x00FF0000) >> 16);
SE_321.data.u8[2] = (((datalayer.battery.info.max_design_voltage_dV * 10) & 0x0000FF00) >> 8);
SE_321.data.u8[3] = ((datalayer.battery.info.max_design_voltage_dV * 10) & 0x000000FF);
//Minimum discharge voltage+2 (eg 10000.00V = 1000000 , 32bits long)
SE_321.data.u8[4] = ((datalayer.battery.info.min_design_voltage_dV * 10) >> 24);
SE_321.data.u8[5] = (((datalayer.battery.info.min_design_voltage_dV * 10) & 0x00FF0000) >> 16);
SE_321.data.u8[6] = (((datalayer.battery.info.min_design_voltage_dV * 10) & 0x0000FF00) >> 8);
SE_321.data.u8[7] = ((datalayer.battery.info.min_design_voltage_dV * 10) & 0x000000FF);
//Maximum charge current+2 (eg 10000.00A = 1000000) TODO: Note s32 bit, which direction?
SE_322.data.u8[0] = ((datalayer.battery.status.max_charge_current_dA * 10) >> 24);
SE_322.data.u8[1] = (((datalayer.battery.status.max_charge_current_dA * 10) & 0x00FF0000) >> 16);
SE_322.data.u8[2] = (((datalayer.battery.status.max_charge_current_dA * 10) & 0x0000FF00) >> 8);
SE_322.data.u8[3] = ((datalayer.battery.status.max_charge_current_dA * 10) & 0x000000FF);
//Maximum discharge current+2 (eg 10000.00A = 1000000) TODO: Note s32 bit, which direction?
SE_322.data.u8[4] = ((datalayer.battery.status.max_discharge_current_dA * 10) >> 24);
SE_322.data.u8[5] = (((datalayer.battery.status.max_discharge_current_dA * 10) & 0x00FF0000) >> 16);
SE_322.data.u8[6] = (((datalayer.battery.status.max_discharge_current_dA * 10) & 0x0000FF00) >> 8);
SE_322.data.u8[7] = ((datalayer.battery.status.max_discharge_current_dA * 10) & 0x000000FF);
//Voltage (ex 370.00 = 37000, 32bits long)
SE_323.data.u8[0] = ((datalayer.battery.status.voltage_dV * 10) >> 24);
SE_323.data.u8[1] = (((datalayer.battery.status.voltage_dV * 10) & 0x00FF0000) >> 16);
SE_323.data.u8[2] = (((datalayer.battery.status.voltage_dV * 10) & 0x0000FF00) >> 8);
SE_323.data.u8[3] = ((datalayer.battery.status.voltage_dV * 10) & 0x000000FF);
//Current (ex 81.00A = 8100) TODO: Note s32 bit, which direction?
SE_323.data.u8[4] = ((datalayer.battery.status.current_dA * 10) >> 24);
SE_323.data.u8[5] = (((datalayer.battery.status.current_dA * 10) & 0x00FF0000) >> 16);
SE_323.data.u8[6] = (((datalayer.battery.status.current_dA * 10) & 0x0000FF00) >> 8);
SE_323.data.u8[7] = ((datalayer.battery.status.current_dA * 10) & 0x000000FF);
//Temperature average
SE_324.data.u8[0] = (temperature_average >> 8);
SE_324.data.u8[1] = (temperature_average & 0x00FF);
//SOC (100.0%)
SE_324.data.u8[2] = ((datalayer.battery.status.reported_soc / 10) >> 8);
SE_324.data.u8[3] = ((datalayer.battery.status.reported_soc / 10) & 0x00FF);
//Commands (enum)
SE_325.data.u8[0] = (commands >> 8);
SE_325.data.u8[1] = (commands & 0x00FF);
//Warnings (enum)
SE_325.data.u8[2] = (warnings >> 8);
SE_325.data.u8[3] = (warnings & 0x00FF);
//Faults (enum)
SE_325.data.u8[4] = (faults >> 8);
SE_325.data.u8[5] = (faults & 0x00FF);
//State (enum)
SE_326.data.u8[0] = (state >> 8);
SE_326.data.u8[1] = (state & 0x00FF);
//Cycle count (OPTIONAL UINT16)
//SE_326.data.u8[2] = Cycle count not tracked by emulator
//SE_326.data.u8[3] = Cycle count not tracked by emulator
//StateOfHealth (OPTIONAL 0-100%)
SE_326.data.u8[4] = (datalayer.battery.status.soh_pptt / 100 >> 8);
SE_326.data.u8[5] = (datalayer.battery.status.soh_pptt / 100 & 0x00FF);
//Capacity (OPTIONAL, full charge) AH+1
SE_326.data.u8[6] = (fully_charged_capacity_ah >> 8);
SE_326.data.u8[7] = (fully_charged_capacity_ah & 0x00FF);
//Cell temp max (OPTIONAL dC)
SE_327.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
SE_327.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
//Cell temp min (OPTIONAL dC)
SE_327.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
SE_327.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
//Cell max volt (OPTIONAL 4.000V)
SE_327.data.u8[4] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
SE_327.data.u8[5] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
//Cell min volt (OPTIONAL 4.000V)
SE_327.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
SE_327.data.u8[7] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
//Lifetime Charge Energy (OPTIONAL, WH, UINT32)
//SE_328.data.u8[0] = Lifetime energy not tracked by emulator
//SE_328.data.u8[1] = Lifetime energy not tracked by emulator
//SE_328.data.u8[2] = Lifetime energy not tracked by emulator
//SE_328.data.u8[3] = Lifetime energy not tracked by emulator
//Lifetime Discharge Energy (OPTIONAL, WH, UINT32)
//SE_328.data.u8[4] = Lifetime energy not tracked by emulator
//SE_328.data.u8[5] = Lifetime energy not tracked by emulator
//SE_328.data.u8[6] = Lifetime energy not tracked by emulator
//SE_328.data.u8[7] = Lifetime energy not tracked by emulator
//Battery Manufacturer ID (UINT16)
//Unique identifier for each battery manufacturer implementing this protocol. IDs must be requested through Schneider Electric Solar.
SE_330.data.u8[0] = 0; //TODO, set Battery Manufacturer ID
SE_330.data.u8[1] = 0; //TODO, set Battery Manufacturer ID
//Battery Model ID (UINT16)
//Unique identifier for each battery model that a manufacturer has implemented this protocol on. IDs must be requested through Schneider Electric Solar.
SE_330.data.u8[2] = 0; //TODO, set Battery Model ID
SE_330.data.u8[3] = 0; //TODO, set Battery Model ID
//Serial numbers
//(For instance ABC123 would be represented as:
//0x41[char5], 0x42[char4], 0x43[char3], 0x31[char2], 0x32 [char1], 0x33 [char0])
SE_330.data.u8[4] = 0x42; //Char 19 - B
SE_330.data.u8[5] = 0x41; //Char 18 - A
SE_330.data.u8[6] = 0x54; //Char 17 - T
SE_330.data.u8[7] = 0x54; //Char 16 - T
SE_331.data.u8[0] = 0x45; //Char 15 - E
SE_331.data.u8[1] = 0x52; //Char 14 - R
SE_331.data.u8[2] = 0x59; //Char 13 - Y
SE_331.data.u8[3] = 0x45; //Char 12 - E
SE_331.data.u8[4] = 0x4D; //Char 11 - M
SE_331.data.u8[5] = 0x55; //Char 10 - U
SE_331.data.u8[6] = 0x4C; //Char 9 - L
SE_331.data.u8[7] = 0x41; //Char 8 - A
SE_332.data.u8[0] = 0x54; //Char 7 - T
SE_332.data.u8[1] = 0x4F; //Char 6 - O
SE_332.data.u8[2] = 0x52; //Char 5 - R
SE_332.data.u8[3] = 0x30; //Char 4 - 0
SE_332.data.u8[4] = 0x31; //Char 3 - 1
SE_332.data.u8[5] = 0x32; //Char 2 - 2
SE_332.data.u8[6] = 0x33; //Char 1 - 3
SE_332.data.u8[7] = 0x34; //Char 0 - 4
//UNIQUE ID
//Schneider Electric Unique string identifier. The value should be an unique string "SEBMS"
SE_333.data.u8[0] = 0x53; //Char 5 - S
SE_333.data.u8[1] = 0x45; //Char 4 - E
SE_333.data.u8[2] = 0x42; //Char 3 - B
SE_333.data.u8[3] = 0x4D; //Char 2 - M
SE_333.data.u8[4] = 0x53; //Char 1 - S
SE_333.data.u8[5] = 0x00; //Char 0 - NULL
//Protocol version, TODO: How do we reply with protocol version 0x0002 ?
SE_320.data.u8[0] = 0x00;
SE_320.data.u8[1] = 0x02;
}
void receive_can_inverter(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x310: // Still alive message from inverter, every 1s
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
break;
default:
break;
}
}
void send_can_inverter() {
unsigned long currentMillis = millis();
// Send 500ms CAN Message
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
previousMillis500ms = currentMillis;
transmit_can(&SE_321, can_config.inverter);
transmit_can(&SE_322, can_config.inverter);
transmit_can(&SE_323, can_config.inverter);
transmit_can(&SE_324, can_config.inverter);
transmit_can(&SE_325, can_config.inverter);
}
// Send 2s CAN Message
if (currentMillis - previousMillis2s >= INTERVAL_2_S) {
previousMillis2s = currentMillis;
transmit_can(&SE_320, can_config.inverter);
transmit_can(&SE_326, can_config.inverter);
transmit_can(&SE_327, can_config.inverter);
}
// Send 10s CAN Message
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
previousMillis10s = currentMillis;
transmit_can(&SE_328, can_config.inverter);
transmit_can(&SE_330, can_config.inverter);
transmit_can(&SE_331, can_config.inverter);
transmit_can(&SE_332, can_config.inverter);
transmit_can(&SE_333, can_config.inverter);
}
}
void setup_inverter(void) { // Performs one time setup
strncpy(datalayer.system.info.inverter_protocol, "Schneider V2 SE BMS CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif

View file

@ -0,0 +1,33 @@
#ifndef SCHNEIDER_CAN_H
#define SCHNEIDER_CAN_H
#include "../include.h"
#define CAN_INVERTER_SELECTED
#define STATE_OFFLINE 0
#define STATE_STANDBY 1
#define STATE_STARTING 2
#define STATE_ONLINE 3
#define STATE_FAULTED 4
// Same enumerations used for Fault and Warning
#define FAULTS_CHARGE_OVERCURRENT 0
#define FAULTS_DISCHARGE_OVERCURRENT 1
#define FAULTS_OVER_TEMPERATURE 2
#define FAULTS_UNDER_TEMPERATURE 3
#define FAULTS_OVER_VOLTAGE 4
#define FAULTS_UNDER_VOLTAGE 5
#define FAULTS_CELL_IMBALANCE 6
#define FAULTS_INTERNAL_COM_ERROR 7
#define FAULTS_SYSTEM_ERROR 8
// Commands. Bit0 forced charge request. Bit1 charge permitted. Bit2 discharge permitted. Bit3 Stop
#define COMMAND_ONLY_CHARGE_ALLOWED 0x02
#define COMMAND_ONLY_DISCHARGE_ALLOWED 0x04
#define COMMAND_CHARGE_AND_DISCHARGE_ALLOWED 0x06
#define COMMAND_STOP 0x08
void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif

View file

@ -128,6 +128,8 @@ void manageSerialLinkTransmitter() {
static unsigned long updateDataTime = 0; static unsigned long updateDataTime = 0;
if (currentTime - updateDataTime > INTERVAL_1_S) { if (currentTime - updateDataTime > INTERVAL_1_S) {
strncpy(datalayer.system.info.inverter_protocol, "Serial link to another LilyGo board", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
updateDataTime = currentTime; updateDataTime = currentTime;
dataLinkTransmit.updateData(0, datalayer.battery.status.real_soc); dataLinkTransmit.updateData(0, datalayer.battery.status.real_soc);
dataLinkTransmit.updateData(1, datalayer.battery.status.soh_pptt); dataLinkTransmit.updateData(1, datalayer.battery.status.soh_pptt);

View file

@ -6,5 +6,6 @@
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h" #include "../lib/mackelec-SerialDataLink/SerialDataLink.h"
void manageSerialLinkTransmitter(); void manageSerialLinkTransmitter();
void setup_inverter(void);
#endif #endif

View file

@ -249,4 +249,9 @@ void send_can_inverter() {
} }
} }
} }
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "SMA CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif #endif

View file

@ -8,5 +8,6 @@
#define STOP_STATE 0x02 #define STOP_STATE 0x02
void transmit_can(CAN_frame* tx_frame, int interface); void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif #endif

View file

@ -320,4 +320,9 @@ void send_tripower_init() {
transmit_can(&SMA_017, can_config.inverter); // Battery Manufacturer transmit_can(&SMA_017, can_config.inverter); // Battery Manufacturer
transmit_can(&SMA_018, can_config.inverter); // Battery Name transmit_can(&SMA_018, can_config.inverter); // Battery Name
} }
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "SMA Tripower CAN", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif #endif

View file

@ -6,5 +6,6 @@
void send_tripower_init(); void send_tripower_init();
void transmit_can(CAN_frame* tx_frame, int interface); void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif #endif

View file

@ -263,4 +263,9 @@ void send_can_inverter() {
transmit_can(&SOFAR_35A, can_config.inverter); transmit_can(&SOFAR_35A, can_config.inverter);
} }
} }
void setup_inverter(void) { // Performs one time setup at startup over CAN bus
strncpy(datalayer.system.info.inverter_protocol, "Sofar BMS (Extended Frame) over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
}
#endif #endif

View file

@ -5,5 +5,6 @@
#define CAN_INVERTER_SELECTED #define CAN_INVERTER_SELECTED
void transmit_can(CAN_frame* tx_frame, int interface); void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif #endif

View file

@ -252,4 +252,9 @@ void receive_can_inverter(CAN_frame rx_frame) {
#endif #endif
} }
} }
void setup_inverter(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.inverter_protocol, "SolaX Triple Power LFP over CAN bus", 63);
datalayer.system.info.inverter_protocol[63] = '\0';
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
}
#endif #endif

View file

@ -15,5 +15,6 @@
#define UPDATING_FW 4 #define UPDATING_FW 4
void transmit_can(CAN_frame* tx_frame, int interface); void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif #endif