Merge branch 'main' into feature/LEAF-30-reset-SOH

This commit is contained in:
Daniel Öster 2024-11-15 23:03:23 +02:00 committed by GitHub
commit 10a7cfec15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 1682 additions and 728 deletions

View file

@ -34,19 +34,25 @@ jobs:
# These are the batteries for which the code will be compiled.
battery:
- BMW_I3_BATTERY
- BMW_IX_BATTERY
- BYD_ATTO_3_BATTERY
- CELLPOWER_BMS
- CHADEMO_BATTERY
- IMIEV_CZERO_ION_BATTERY
- JAGUAR_IPACE_BATTERY
- KIA_HYUNDAI_64_BATTERY
- KIA_E_GMP_BATTERY
- KIA_HYUNDAI_HYBRID_BATTERY
- MG_5_BATTERY
- NISSAN_LEAF_BATTERY
- PYLON_BATTERY
- RJXZS_BMS
- RANGE_ROVER_PHEV_BATTERY
- RENAULT_KANGOO_BATTERY
- RENAULT_TWIZY_BATTERY
- RENAULT_ZOE_GEN1_BATTERY
- RENAULT_ZOE_GEN2_BATTERY
- SANTA_FE_PHEV_BATTERY
- TESLA_MODEL_3Y_BATTERY
- VOLVO_SPA_BATTERY
- TEST_FAKE_BATTERY
@ -54,13 +60,6 @@ jobs:
# These are the emulated inverter communication protocols for which the code will be compiled.
inverter:
- 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.
runs-on: ubuntu-latest

View file

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

View file

@ -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
ACAN2515 can(MCP2515_CS, SPI, MCP2515_INT);
static ACAN2515_Buffer16 gBuffer;
#endif
#endif //DUAL_CAN
#ifdef CAN_FD
#include "src/lib/pierremolinaro-ACAN2517FD/ACAN2517FD.h"
ACAN2517FD canfd(MCP2517_CS, SPI, MCP2517_INT);
#else
typedef char CANFDMessage;
#endif
#endif //CAN_FD
// ModbusRTU parameters
#ifdef MODBUS_INVERTER_SELECTED
@ -172,11 +170,10 @@ void setup() {
init_rs485();
init_serialDataLink();
init_inverter();
init_battery();
#if defined(CAN_INVERTER_SELECTED) || defined(MODBUS_INVERTER_SELECTED)
setup_inverter();
#endif
setup_battery();
#ifdef EQUIPMENT_STOP_BUTTON
init_equipment_stop_button();
#endif
@ -293,7 +290,7 @@ void core_loop(void* task_time_us) {
#ifdef DOUBLE_BATTERY
update_values_battery2();
#endif
update_scaled_values(); // Check if real or calculated SOC% value should be sent
update_calculated_values();
#ifndef SERIAL_LINK_RECEIVER
update_machineryprotection(); // Check safeties (Not on serial link reciever board)
#endif
@ -401,11 +398,11 @@ void init_stored_settings() {
}
temp = settings.getUInt("MAXCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.info.max_charge_amp_dA = temp;
datalayer.battery.settings.max_user_set_charge_dA = temp;
}
temp = settings.getUInt("MAXDISCHARGEAMP", false);
if (temp != 0) {
datalayer.battery.info.max_discharge_amp_dA = temp;
datalayer.battery.settings.max_user_set_discharge_dA = temp;
temp = settings.getBool("USE_SCALED_SOC", false);
datalayer.battery.settings.soc_scaling_active = temp; //This bool needs to be checked inside the temp!= block
} // No way to know if it wasnt reset otherwise
@ -554,29 +551,6 @@ void init_rs485() {
#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
void monitor_equipment_stop_button() {
@ -614,31 +588,32 @@ void init_equipment_stop_button() {
#endif
#ifdef CAN_FD
// Functions
#ifdef DEBUG_CANFD_DATA
enum frameDirection { MSG_RX, MSG_TX };
void print_canfd_frame(CANFDMessage rx_frame, frameDirection msgDir); // Needs to be declared before it is defined
void print_canfd_frame(CANFDMessage rx_frame, frameDirection msgDir) {
int i = 0;
(msgDir == 0) ? Serial.print("RX ") : Serial.print("TX ");
Serial.print(rx_frame.id, HEX);
enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1
void print_can_frame(CAN_frame frame, frameDirection msgDir);
void print_can_frame(CAN_frame frame, frameDirection msgDir) {
uint8_t i = 0;
Serial.print(millis());
Serial.print(" ");
for (i = 0; i < rx_frame.len; i++) {
Serial.print(rx_frame.data[i] < 16 ? "0" : "");
Serial.print(rx_frame.data[i], HEX);
(msgDir == 0) ? Serial.print("RX ") : Serial.print("TX ");
Serial.print(frame.ID, HEX);
Serial.print(" ");
Serial.print(frame.DLC);
Serial.print(" ");
for (i = 0; i < frame.DLC; i++) {
Serial.print(frame.data.u8[i] < 16 ? "0" : "");
Serial.print(frame.data.u8[i], HEX);
Serial.print(" ");
}
Serial.println(" ");
}
#endif
#ifdef CAN_FD
// Functions
void receive_canfd() { // This section checks if we have a complete CAN-FD message incoming
CANFDMessage frame;
if (canfd.available()) {
canfd.receive(frame);
#ifdef DEBUG_CANFD_DATA
print_canfd_frame(frame, frameDirection(MSG_RX));
#endif
CAN_frame rx_frame;
rx_frame.ID = frame.id;
rx_frame.ext_ID = frame.ext;
@ -837,7 +812,26 @@ void handle_contactors() {
#endif // CONTACTOR_CONTROL
}
void update_scaled_values() {
void update_calculated_values() {
/* Calculate allowed charge/discharge currents*/
if (datalayer.battery.status.voltage_dV > 10) {
// Only update value when we have voltage available to avoid div0. TODO: This should be based on nominal voltage
datalayer.battery.status.max_charge_current_dA =
((datalayer.battery.status.max_charge_power_W * 100) / datalayer.battery.status.voltage_dV);
datalayer.battery.status.max_discharge_current_dA =
((datalayer.battery.status.max_discharge_power_W * 100) / datalayer.battery.status.voltage_dV);
}
/* Restrict values from user settings if needed*/
if (datalayer.battery.status.max_charge_current_dA > datalayer.battery.settings.max_user_set_charge_dA) {
datalayer.battery.status.max_charge_current_dA = datalayer.battery.settings.max_user_set_charge_dA;
}
if (datalayer.battery.status.max_discharge_current_dA > datalayer.battery.settings.max_user_set_discharge_dA) {
datalayer.battery.status.max_discharge_current_dA = datalayer.battery.settings.max_user_set_discharge_dA;
}
/* Calculate active power based on voltage and current*/
datalayer.battery.status.active_power_W =
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
if (datalayer.battery.settings.soc_scaling_active) {
/** SOC Scaling
*
@ -882,6 +876,9 @@ void update_scaled_values() {
}
#ifdef DOUBLE_BATTERY
/* Calculate active power based on voltage and current*/
datalayer.battery2.status.active_power_W =
(datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
// Calculate the scaled remaining capacity in Wh
if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery2.status.real_soc > 0) {
@ -896,7 +893,7 @@ void update_scaled_values() {
}
#endif
} else { // No SOC window wanted. Set scaled to same as real.
} else { // soc_scaling_active == false. No SOC window wanted. Set scaled to same as real.
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
#ifdef DOUBLE_BATTERY
@ -975,8 +972,8 @@ void storeSettings() {
datalayer.battery.settings.max_percentage / 10); // Divide by 10 for backwards compatibility
settings.putUInt("MINPERCENTAGE",
datalayer.battery.settings.min_percentage / 10); // Divide by 10 for backwards compatibility
settings.putUInt("MAXCHARGEAMP", datalayer.battery.info.max_charge_amp_dA);
settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.info.max_discharge_amp_dA);
settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA);
settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA);
settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active);
settings.end();
}
@ -1062,6 +1059,9 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
if (!allowed_to_send_CAN) {
return;
}
#ifdef DEBUG_CAN_DATA
print_can_frame(*tx_frame, frameDirection(MSG_TX));
#endif //DEBUG_CAN_DATA
switch (interface) {
case CAN_NATIVE:
@ -1104,10 +1104,6 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
send_ok = canfd.tryToSend(MCP2518Frame);
if (!send_ok) {
set_event(EVENT_CANFD_BUFFER_FULL, interface);
} else {
#ifdef DEBUG_CANFD_DATA
print_canfd_frame(MCP2518Frame, frameDirection(MSG_TX));
#endif
}
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
@ -1120,6 +1116,10 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
}
void receive_can(CAN_frame* rx_frame, int interface) {
#ifdef DEBUG_CAN_DATA
print_can_frame(*rx_frame, frameDirection(MSG_RX));
#endif //DEBUG_CAN_DATA
if (interface == can_config.battery) {
receive_can_battery(*rx_frame);
}

View file

@ -10,6 +10,7 @@
/* Select battery used */
//#define BMW_I3_BATTERY
//#define BMW_IX_BATTERY
//#define BYD_ATTO_3_BATTERY
//#define CELLPOWER_BMS
//#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below
@ -22,6 +23,7 @@
//#define NISSAN_LEAF_BATTERY
//#define PYLON_BATTERY
//#define RJXZS_BMS
//#define RANGE_ROVER_PHEV_BATTERY
//#define RENAULT_KANGOO_BATTERY
//#define RENAULT_TWIZY_BATTERY
//#define RENAULT_ZOE_GEN1_BATTERY
@ -49,10 +51,11 @@
/* Select hardware used for Battery-Emulator */
#define HW_LILYGO
//#define HW_STARK
//#define HW_3LB
/* Other options */
//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs (WARNING, raises CPU load, do not use for production)
//#define DEBUG_CANFD_DATA //Enable this line to have the USB port output CAN-FD data while program runs (WARNING, raises CPU load, do not use for production)
//#define DEBUG_CAN_DATA //Enable this line to print incoming/outgoing CAN & CAN-FD messages to USB serial (WARNING, raises CPU load, do not use for production)
//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting
//#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM for CONTACTOR_CONTROL, which lowers power consumption and heat generation. CONTACTOR_CONTROL must be enabled.

View file

@ -6,6 +6,10 @@
#include "BMW-I3-BATTERY.h"
#endif
#ifdef BMW_IX_BATTERY
#include "BMW-IX-BATTERY.h"
#endif
#ifdef BYD_ATTO_3_BATTERY
#include "BYD-ATTO-3-BATTERY.h"
#endif
@ -54,6 +58,10 @@
#include "RJXZS-BMS.h"
#endif
#ifdef RANGE_ROVER_PHEV_BATTERY
#include "RANGE-ROVER-PHEV-BATTERY.h"
#endif
#ifdef RENAULT_KANGOO_BATTERY
#include "RENAULT-KANGOO-BATTERY.h"
#endif

View file

@ -239,7 +239,6 @@ static int16_t battery_temperature_max = 0;
static int16_t battery_temperature_min = 0;
static int16_t battery_max_charge_amperage = 0;
static int16_t battery_max_discharge_amperage = 0;
static int16_t battery_power = 0;
static int16_t battery_current = 0;
static uint8_t battery_status_error_isolation_external_Bordnetz = 0;
static uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
@ -308,7 +307,6 @@ static int16_t battery2_temperature_max = 0;
static int16_t battery2_temperature_min = 0;
static int16_t battery2_max_charge_amperage = 0;
static int16_t battery2_max_discharge_amperage = 0;
static int16_t battery2_power = 0;
static int16_t battery2_current = 0;
static uint8_t battery2_status_error_isolation_external_Bordnetz = 0;
static uint8_t battery2_status_error_isolation_internal_Bordnetz = 0;
@ -388,10 +386,6 @@ void update_values_battery2() { //This function maps all the values fetched via
datalayer.battery2.status.max_charge_power_W = battery2_BEV_available_power_longterm_charge;
}
battery2_power = (datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
datalayer.battery2.status.active_power_W = battery2_power;
datalayer.battery2.status.temperature_min_dC = battery2_temperature_min * 10; // Add a decimal
datalayer.battery2.status.temperature_max_dC = battery2_temperature_max * 10; // Add a decimal
@ -456,10 +450,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
datalayer.battery.status.active_power_W = battery_power;
datalayer.battery.status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal
datalayer.battery.status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal
@ -1128,9 +1118,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("BMW i3 battery selected");
#endif //DEBUG_VIA_USB
strncpy(datalayer.system.info.battery_protocol, "BMW i3", 63);
datalayer.system.info.battery_protocol[63] = '\0';
//Before we have started up and detected which battery is in use, use 60AH values
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
@ -1139,9 +1128,6 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.system.status.battery_allows_contactor_closing = true;
#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.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;

View file

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

View file

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

View file

@ -122,9 +122,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W = 10000; //TODO: Map from CAN later on
datalayer.battery.status.active_power_W =
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
datalayer.battery.status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
datalayer.battery.status.cell_min_voltage_mV = BMS_lowest_cell_voltage_mV;
@ -402,9 +399,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("BYD Atto 3 battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "BYD Atto 3", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 126;
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
@ -445,9 +441,6 @@ void update_values_battery2() { //This function maps all the values fetched via
datalayer.battery2.status.max_charge_power_W = 10000; //TODO: Map from CAN later on
datalayer.battery2.status.active_power_W =
(datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
datalayer.battery2.status.cell_max_voltage_mV = BMS2_highest_cell_voltage_mV;
datalayer.battery2.status.cell_min_voltage_mV = BMS2_lowest_cell_voltage_mV;

View file

@ -124,9 +124,6 @@ void update_values_battery() {
datalayer.battery.status.current_dA = battery_pack_current_dA;
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.max_charge_power_W = 5000; //TODO, is this available via CAN?
datalayer.battery.status.max_discharge_power_W = 5000; //TODO, is this available via CAN?
@ -336,9 +333,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Cellpower BMS selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Cellpower BMS", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -1031,9 +1031,9 @@ void handle_chademo_sequence() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Chademo battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Chademo V2X mode", 63);
datalayer.system.info.battery_protocol[63] = '\0';
CHADEMO_Status = CHADEMO_IDLE;

View file

@ -51,8 +51,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_discharge_power_W = 10000; // 10kW //TODO: Fix when CAN is decoded
datalayer.battery.status.active_power_W = BMU_Power; //TODO: Scaling?
static int n = sizeof(cell_voltages) / sizeof(cell_voltages[0]);
max_volt_cel = cell_voltages[0]; // Initialize max with the first element of the array
for (int i = 1; i < n; i++) {
@ -226,9 +224,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Mitsubishi i-MiEV / Citroen C-Zero / Peugeot Ion battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "I-Miev / C-Zero / Ion Triplet", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;

View file

@ -81,10 +81,6 @@ void update_values_battery() {
datalayer.battery.status.cell_min_voltage_mV = HVBattCellVoltageMinMv;
//Power in watts, Negative = charging batt
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.temperature_min_dC = HVBattCellTempColdest * 10; // C to dC
datalayer.battery.status.temperature_max_dC = HVBattCellTempHottest * 10; // C to dC
@ -258,10 +254,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Jaguar iPace 90kWh battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Jaguar I-PACE", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 108;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -38,7 +38,6 @@ static uint16_t CellVoltMin_mV = 3700;
static uint16_t batteryVoltage = 6700;
static int16_t leadAcidBatteryVoltage = 120;
static int16_t batteryAmps = 0;
static int16_t powerWatt = 0;
static int16_t temperatureMax = 0;
static int16_t temperatureMin = 0;
static int16_t allowedDischargePower = 0;
@ -660,10 +659,6 @@ void update_values_battery() { //This function maps all the values fetched via
//The allowed discharge power is not available. We hardcode this value for now
datalayer.battery.status.max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
powerWatt = ((batteryVoltage * batteryAmps) / 100);
datalayer.battery.status.active_power_W = powerWatt; //Power in watts, Negative = charging batt
datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery.status.temperature_max_dC = (int8_t)temperatureMax * 10; //Increase decimals, 18C -> 18.0C
@ -1042,14 +1037,12 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Hyundai E-GMP (Electric Global Modular Platform) battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai EGMP platform", 63);
datalayer.system.info.battery_protocol[63] = '\0';
startMillis = millis(); // Record the starting time
datalayer.system.status.battery_allows_contactor_closing = true;
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.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -124,10 +124,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_discharge_power_W = allowedDischargePower * 10;
//Power in watts, Negative = charging batt
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
datalayer.battery.status.temperature_max_dC = (int8_t)temperatureMax * 10; //Increase decimals, 18C -> 18.0C
@ -538,9 +534,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Kia Niro / Hyundai Kona 64kWh battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Kia/Hyundai 64/40kWh battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_98S_DV; //Start with 98S value. Precised later
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_90S_DV; //Start with 90S value. Precised later
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;

View file

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

View file

@ -39,8 +39,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W;
datalayer.battery.status.active_power_W;
datalayer.battery.status.temperature_min_dC;
datalayer.battery.status.temperature_max_dC;
@ -137,9 +135,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("MG 5 battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "MG 5 battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -203,9 +203,6 @@ void update_values_battery() { /* This function maps all the values fetched via
datalayer.battery.status.remaining_capacity_Wh = battery_Wh_Remaining;
datalayer.battery.status.active_power_W = ((battery_Total_Voltage2 * battery_Current2) /
4); //P = U * I (Both values are 0.5 per bit so the math is non-intuitive)
//Update temperature readings. Method depends on which generation LEAF battery is used
if (LEAF_battery_Type == ZE0_BATTERY) {
//Since we only have average value, send the minimum as -1.0 degrees below average
@ -376,10 +373,6 @@ void update_values_battery2() { // Handle the values coming in from battery #2
datalayer.battery2.status.remaining_capacity_Wh = battery2_Wh_Remaining;
datalayer.battery2.status.active_power_W =
((battery2_Total_Voltage2 * battery2_Current2) /
4); //P = U * I (Both values are 0.5 per bit so the math is non-intuitive)
//Update temperature readings. Method depends on which generation LEAF battery is used
if (LEAF_battery2_Type == ZE0_BATTERY) {
//Since we only have average value, send the minimum as -1.0 degrees below average
@ -1438,6 +1431,8 @@ void decodeChallengeData(unsigned int incomingChallenge, unsigned char* solvedCh
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -60,9 +60,6 @@ void update_values_battery() {
datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0) , invert the sign
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.max_charge_power_W = (max_charge_current * (voltage_dV / 10));
datalayer.battery.status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10));
@ -178,10 +175,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Pylon battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Pylon compatible battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;

View file

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

View file

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

View file

@ -95,9 +95,6 @@ void update_values_battery() { //This function maps all the values fetched via
//The above value is 0 on some packs. We instead hardcode this now.
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_W;
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.temperature_min_dC = (LB_MIN_TEMPERATURE * 10);
datalayer.battery.status.temperature_max_dC = (LB_MAX_TEMPERATURE * 10);
@ -237,9 +234,9 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Renault Kangoo battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Renault Kangoo", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -46,9 +46,6 @@ void update_values_battery() {
datalayer.battery.status.current_dA = current_dA; //value is *10 (150 = 15.0)
datalayer.battery.status.remaining_capacity_Wh = remaining_capacity_Wh;
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
// The twizy provides two values: one for the maximum charge provided by the on-board charger
// and one for the maximum charge during recuperation.
// For now we use the lower of the two (usually the charger one)
@ -135,10 +132,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Renault Twizy battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Renault Twizy", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 14;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -104,10 +104,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W = 50;
}
//Power in watts, Negative = charging batt
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
int16_t temperatures[] = {cell_1_temperature_polled, cell_2_temperature_polled, cell_3_temperature_polled,
cell_4_temperature_polled, cell_5_temperature_polled, cell_6_temperature_polled,
cell_7_temperature_polled, cell_8_temperature_polled, cell_9_temperature_polled,
@ -522,9 +518,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Renault Zoe 22/40kWh battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen1 22/40kWh", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -166,9 +166,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W = battery_max_generated * 10;
datalayer.battery.status.active_power_W =
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
datalayer.battery.status.temperature_min_dC = ((battery_min_temp - 640) * 0.625);
datalayer.battery.status.temperature_max_dC = ((battery_max_temp - 640) * 0.625);
@ -388,9 +385,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Renault Zoe 50kWh battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Renault Zoe Gen2 50kWh", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;

View file

@ -98,9 +98,6 @@ void update_values_battery() {
datalayer.battery.status.current_dA = total_current;
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
// Charge power is set in .h file
if (datalayer.battery.status.real_soc > 9900) {
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W;
@ -573,10 +570,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("RJXZS BMS selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "RJXZS BMS, DIY battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;

View file

@ -85,10 +85,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W = allowedChargePower * 10;
//Power in watts, Negative = charging batt
datalayer.battery.status.active_power_W =
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.cell_max_voltage_mV = CellVoltMax_mV;
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
@ -406,9 +402,8 @@ uint8_t CalculateCRC8(CAN_frame rx_frame) {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Hyundai Santa Fe PHEV battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Santa Fe PHEV", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_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) {
Serial.println("SERIAL_DATA_LINK_RECEIVER selected");
strncpy(datalayer.system.info.battery_protocol, "Serial link to another LilyGo board", 63);
datalayer.system.info.battery_protocol[63] = '\0';
}
// Needed to make the compiler happy
void update_values_battery() {}

View file

@ -22,7 +22,7 @@ CAN_frame TESLA_221_2 = {
.DLC = 8,
.ID = 0x221,
.data = {0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA}}; //Contactor Frame 221 - hv_up_for_drive
static uint16_t sendContactorClosingMessagesStill = 300;
static uint32_t battery_total_discharge = 0;
static uint32_t battery_total_charge = 0;
static uint16_t battery_volts = 0; // V
@ -297,8 +297,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
}
datalayer.battery.status.active_power_W = ((battery_volts / 10) * battery_amps);
datalayer.battery.status.temperature_min_dC = battery_min_temp;
datalayer.battery.status.temperature_max_dC = battery_max_temp;
@ -857,8 +855,6 @@ void update_values_battery2() { //This function maps all the values fetched via
datalayer.battery2.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
}
datalayer.battery2.status.active_power_W = ((battery2_volts / 10) * battery2_amps);
datalayer.battery2.status.temperature_min_dC = battery2_min_temp;
datalayer.battery2.status.temperature_max_dC = battery2_max_temp;
@ -1003,7 +999,7 @@ unsigned long lastSend118 = 0;
int index_1CF = 0;
int index_118 = 0;
#endif
#endif //defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL)
void send_can_battery() {
/*From bielec: My fist 221 message, to close the contactors is 0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96 and then,
@ -1014,13 +1010,13 @@ the first, for a few cycles, then stop all messages which causes the contactor
unsigned long currentMillis = millis();
#if defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL)
if (datalayer.system.status.inverter_allows_contactor_closing) {
if ((datalayer.system.status.inverter_allows_contactor_closing) && (datalayer.battery.status.bms_status != FAULT)) {
if (currentMillis - lastSend1CF >= 10) {
transmit_can(&can_msg_1CF[index_1CF], can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&can_msg_1CF[index_1CF], can_config.battery_double);
#endif
#endif // DOUBLE_BATTERY
index_1CF = (index_1CF + 1) % 8;
lastSend1CF = currentMillis;
@ -1030,7 +1026,7 @@ the first, for a few cycles, then stop all messages which causes the contactor
transmit_can(&can_msg_118[index_118], can_config.battery);
#ifdef DOUBLE_BATTERY
transmit_can(&can_msg_1CF[index_1CF], can_config.battery_double);
#endif
#endif //DOUBLE_BATTERY
index_118 = (index_118 + 1) % 16;
lastSend118 = currentMillis;
@ -1039,7 +1035,7 @@ the first, for a few cycles, then stop all messages which causes the contactor
index_1CF = 0;
index_118 = 0;
}
#endif
#endif //defined(TESLA_MODEL_SX_BATTERY) || defined(EXP_TESLA_BMS_DIGITAL_HVIL)
//Send 30ms message
if (currentMillis - previousMillis30 >= INTERVAL_30_MS) {
@ -1051,15 +1047,26 @@ the first, for a few cycles, then stop all messages which causes the contactor
}
previousMillis30 = currentMillis;
if (datalayer.system.status.inverter_allows_contactor_closing) {
if ((datalayer.system.status.inverter_allows_contactor_closing == true) &&
(datalayer.battery.status.bms_status != FAULT)) {
sendContactorClosingMessagesStill = 300;
transmit_can(&TESLA_221_1, can_config.battery);
transmit_can(&TESLA_221_2, can_config.battery);
#ifdef DOUBLE_BATTERY
if (datalayer.system.status.battery2_allows_contactor_closing) {
transmit_can(&TESLA_221_1, can_config.battery_double); // CAN2 connected to battery 2
transmit_can(&TESLA_221_1, can_config.battery_double);
transmit_can(&TESLA_221_2, can_config.battery_double);
}
#endif //DOUBLE_BATTERY
} else { // Faulted state, or inverter blocks contactor closing
if (sendContactorClosingMessagesStill > 0) {
transmit_can(&TESLA_221_1, can_config.battery);
sendContactorClosingMessagesStill--;
#ifdef DOUBLE_BATTERY
transmit_can(&TESLA_221_1, can_config.battery_double);
#endif //DOUBLE_BATTERY
}
}
}
}
@ -1091,7 +1098,8 @@ void printFaultCodesIfActive() {
}
if (datalayer.system.status.inverter_allows_contactor_closing == false) {
Serial.println(
"ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter OR "
"ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter "
"OR "
"disable the inverter protocol to proceed with contactor closing");
}
// Check each symbol and print debug information if its value is 1
@ -1166,7 +1174,8 @@ void printFaultCodesIfActive_battery2() {
}
if (datalayer.system.status.inverter_allows_contactor_closing == false) {
Serial.println(
"ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter OR "
"ERROR: Solar inverter does not allow for contactor closing. Check communication connection to the inverter "
"OR "
"disable the inverter protocol to proceed with contactor closing");
}
// Check each symbol and print debug information if its value is 1
@ -1240,13 +1249,11 @@ void printDebugIfActive(uint8_t symbol, const char* message) {
}
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;
#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.min_design_voltage_dV = MIN_PACK_VOLTAGE_SX_NCMA;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_NCA_NCM;
@ -1262,6 +1269,8 @@ void setup_battery(void) { // Performs one time setup at startup
#endif // TESLA_MODEL_SX_BATTERY
#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
datalayer.battery.info.chemistry = battery_chemistry_enum::LFP;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_3Y_LFP;

View file

@ -40,8 +40,6 @@ void update_values_battery() { /* This function puts fake values onto the parame
datalayer.battery.status.cell_min_voltage_mV = 3500;
datalayer.battery.status.active_power_W = 0; // 0W
datalayer.battery.status.temperature_min_dC = 50; // 5.0*C
datalayer.battery.status.temperature_max_dC = 60; // 6.0*C
@ -95,8 +93,6 @@ void update_values_battery2() { // Handle the values coming in from battery #2
datalayer.battery2.status.cell_min_voltage_mV = 3500;
datalayer.battery2.status.active_power_W = 0; // 0W
datalayer.battery2.status.temperature_min_dC = 50; // 5.0*C
datalayer.battery2.status.temperature_max_dC = 60; // 6.0*C
@ -130,35 +126,11 @@ void update_values_battery2() { // Handle the values coming in from battery #2
void receive_can_battery2(CAN_frame rx_frame) {
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
// All CAN messages recieved will be logged via serial
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.ID, HEX);
Serial.print(" ");
Serial.print(rx_frame.DLC);
Serial.print(" ");
for (int i = 0; i < rx_frame.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
}
Serial.println("");
}
#endif // DOUBLE_BATTERY
void receive_can_battery(CAN_frame rx_frame) {
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
// All CAN messages recieved will be logged via serial
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
Serial.print(" ");
Serial.print(rx_frame.ID, HEX);
Serial.print(" ");
Serial.print(rx_frame.DLC);
Serial.print(" ");
for (int i = 0; i < rx_frame.DLC; ++i) {
Serial.print(rx_frame.data.u8[i], HEX);
Serial.print(" ");
}
Serial.println("");
}
void send_can_battery() {
unsigned long currentMillis = millis();
@ -173,9 +145,8 @@ void send_can_battery() {
void setup_battery(void) { // Performs one time setup at startup
randomSeed(analogRead(0));
#ifdef DEBUG_VIA_USB
Serial.println("Test mode with fake battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.max_design_voltage_dV =
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)

View file

@ -83,7 +83,6 @@ void update_values_battery() { //This function maps all the values fetched via
//datalayer.battery.status.max_discharge_power_W = HvBattPwrLimDchaSoft * 1000; // Use power limit reported from BMS, not trusted ATM
datalayer.battery.status.max_discharge_power_W = 30000;
datalayer.battery.status.max_charge_power_W = 30000;
datalayer.battery.status.active_power_W = (BATT_U)*BATT_I;
datalayer.battery.status.temperature_min_dC = BATT_T_MIN;
datalayer.battery.status.temperature_max_dC = BATT_T_MAX;
@ -333,10 +332,8 @@ void send_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Volvo SPA XC40 Recharge / Polestar2 78kWh battery selected");
#endif
strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 78kWh battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 108;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;

View file

@ -19,10 +19,6 @@ typedef struct {
uint16_t min_cell_voltage_mV = 2700;
/** The maxumum allowed deviation between cells, in milliVolt. 500 = 0.500 V */
uint16_t max_cell_voltage_deviation_mV = 500;
/** BYD CAN specific setting, max charge in deciAmpere. 300 = 30.0 A */
uint16_t max_charge_amp_dA = BATTERY_MAX_CHARGE_AMP;
/** BYD CAN specific setting, max discharge in deciAmpere. 300 = 30.0 A */
uint16_t max_discharge_amp_dA = BATTERY_MAX_DISCHARGE_AMP;
/** uint8_t */
/** Total number of cells in the pack */
@ -35,7 +31,7 @@ typedef struct {
typedef struct {
/** int32_t */
/** Instantaneous battery power in Watts */
/** Instantaneous battery power in Watts. Calculated based on voltage_dV and current_dA */
/* Positive value = Battery Charging */
/* Negative value = Battery Discharging */
int32_t active_power_W;
@ -49,10 +45,14 @@ typedef struct {
*/
uint32_t reported_remaining_capacity_Wh;
/** Maximum allowed battery discharge power in Watts */
/** Maximum allowed battery discharge power in Watts. Set by battery */
uint32_t max_discharge_power_W = 0;
/** Maximum allowed battery charge power in Watts */
/** Maximum allowed battery charge power in Watts. Set by battery */
uint32_t max_charge_power_W = 0;
/** Maximum allowed battery discharge current in dA. Calculated based on allowed W and Voltage */
uint16_t max_discharge_current_dA = 0;
/** Maximum allowed battery charge current in dA. Calculated based on allowed W and Voltage */
uint16_t max_charge_current_dA = 0;
/** int16_t */
/** Maximum temperature currently measured in the pack, in d°C. 150 = 15.0 °C */
@ -107,6 +107,10 @@ typedef struct {
* you want the inverter to be able to use. At this real SOC, the inverter
* will "see" 100% */
uint16_t max_percentage = BATTERY_MAXPERCENTAGE;
/** The user specified maximum allowed charge rate, in deciAmpere. 300 = 30.0 A */
uint16_t max_user_set_charge_dA = BATTERY_MAX_CHARGE_AMP;
/** The user specified maximum allowed discharge rate, in deciAmpere. 300 = 30.0 A */
uint16_t max_user_set_discharge_dA = BATTERY_MAX_DISCHARGE_AMP;
} DATALAYER_BATTERY_SETTINGS_TYPE;
typedef struct {
@ -123,7 +127,10 @@ typedef struct {
} DATALAYER_SHUNT_TYPE;
typedef struct {
// TODO
/** array with type of battery used, for displaying on webserver */
char battery_protocol[64] = {0};
/** array with type of inverter used, for displaying on webserver */
char inverter_protocol[64] = {0};
} DATALAYER_SYSTEM_INFO_TYPE;
typedef struct {

View file

@ -3,6 +3,31 @@
#include "../include.h"
typedef struct {
/** uint16_t */
/** Terminal 30 - 12V SME Supply Voltage */
uint16_t T30_Voltage = 0;
/** Status HVIL, 1 HVIL OK, 0 HVIL disconnected*/
uint8_t hvil_status = 0;
/** Min/Max Cell SOH*/
uint16_t min_soh_state = 0;
uint16_t max_soh_state = 0;
uint32_t bms_uptime = 0;
uint8_t pyro_status_pss1 = 0;
uint8_t pyro_status_pss4 = 0;
uint8_t pyro_status_pss6 = 0;
int32_t iso_safety_positive = 0;
int32_t iso_safety_negative = 0;
int32_t iso_safety_parallel = 0;
int32_t allowable_charge_amps = 0;
int32_t allowable_discharge_amps = 0;
int16_t balancing_status = 0;
int16_t battery_voltage_after_contactor = 0;
unsigned long min_cell_voltage_data_age = 0;
unsigned long max_cell_voltage_data_age = 0;
} DATALAYER_INFO_BMWIX;
typedef struct {
/** uint16_t */
/** SOC% raw battery value. Might not always reach 100% */
@ -264,6 +289,7 @@ typedef struct {
class DataLayerExtended {
public:
DATALAYER_INFO_BMWIX bmwix;
DATALAYER_INFO_BMWI3 bmwi3;
DATALAYER_INFO_BYDATTO3 bydAtto3;
DATALAYER_INFO_CELLPOWER cellpower;

View file

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

View file

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

View file

@ -229,6 +229,14 @@ void update_machineryprotection() {
}
#endif // DOUBLE_BATTERY
//Safeties verified, Zero charge/discharge ampere values incase any safety wrote the W to 0
if (datalayer.battery.status.max_discharge_power_W == 0) {
datalayer.battery.status.max_discharge_current_dA = 0;
}
if (datalayer.battery.status.max_charge_power_W == 0) {
datalayer.battery.status.max_charge_current_dA = 0;
}
}
//battery pause status begin

View file

@ -16,6 +16,45 @@ String advanced_battery_processor(const String& var) {
// Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
#ifdef BMW_IX_BATTERY
content +=
"<h4>Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) +
" dV</h4>";
content += "<h4>Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV</h4>";
content += "<h4>Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV</h4>";
content += "<h4>Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV</h4>";
content += "<h4>Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV</h4>";
content +=
"<h4>Min Cell Voltage Data Age: " + String(datalayer_extended.bmwix.min_cell_voltage_data_age) + " ms</h4>";
content +=
"<h4>Max Cell Voltage Data Age: " + String(datalayer_extended.bmwix.max_cell_voltage_data_age) + " ms</h4>";
content += "<h4>Allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W</h4>";
content += "<h4>Allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W</h4>";
content += "<h4>T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV</h4>";
content += "<h4>Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "</h4>";
static const char* balanceText[5] = {"0 No balancing mode active", "1 Voltage-Controlled Balancing Mode",
"2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging",
"3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage",
"4 No balancing mode active, qualifier invalid"};
content += "<h4>Balancing: " + String((balanceText[datalayer_extended.bmwix.balancing_status])) + "</h4>";
static const char* hvilText[2] = {"Error (Loop Open)", "OK (Loop Closed)"};
content += "<h4>HVIL Status: " + String(hvilText[datalayer_extended.bmwix.hvil_status]) + "</h4>";
content += "<h4>BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds</h4>";
content += "<h4>BMS Allowed Charge Amps: " + String(datalayer_extended.bmwix.allowable_charge_amps) + " A</h4>";
content +=
"<h4>BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A</h4>";
content += "<br>";
content += "<h3>HV Isolation (2147483647kOhm = maximum/invalid)</h3>";
content += "<h4>Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm</h4>";
content += "<h4>Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm</h4>";
content += "<h4>Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm</h4>";
static const char* pyroText[5] = {"0 Value Invalid", "1 Successfully Blown", "2 Disconnected",
"3 Not Activated - Pyro Intact", "4 Unknown"};
content += "<h4>Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss1])) + "</h4>";
content += "<h4>Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss4])) + "</h4>";
content += "<h4>Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss6])) + "</h4>";
#endif //BMW_IX_BATTERY
#ifdef BMW_I3_BATTERY
content += "<h4>SOC raw: " + String(datalayer_extended.bmwi3.SOC_raw) + "</h4>";
content += "<h4>SOC dash: " + String(datalayer_extended.bmwi3.SOC_dash) + "</h4>";

View file

@ -57,11 +57,11 @@ String settings_processor(const String& var) {
content += "<h4 style='color: " + String(datalayer.battery.settings.soc_scaling_active ? "white" : "darkgrey") +
";'>SOC min percentage: " + String(datalayer.battery.settings.min_percentage / 100.0, 1) +
" </span> <button onclick='editSocMin()'>Edit</button></h4>";
content +=
"<h4 style='color: white;'>Max charge speed: " + String(datalayer.battery.info.max_charge_amp_dA / 10.0, 1) +
" A </span> <button onclick='editMaxChargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Max charge speed: " +
String(datalayer.battery.settings.max_user_set_charge_dA / 10.0, 1) +
" A </span> <button onclick='editMaxChargeA()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Max discharge speed: " +
String(datalayer.battery.info.max_discharge_amp_dA / 10.0, 1) +
String(datalayer.battery.settings.max_user_set_discharge_dA / 10.0, 1) +
" A </span> <button onclick='editMaxDischargeA()'>Edit</button></h4>";
// Close the block
content += "</div>";

View file

@ -210,7 +210,7 @@ void init_webserver() {
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.info.max_charge_amp_dA = static_cast<uint16_t>(value.toFloat() * 10);
datalayer.battery.settings.max_user_set_charge_dA = static_cast<uint16_t>(value.toFloat() * 10);
storeSettings();
request->send(200, "text/plain", "Updated successfully");
} else {
@ -224,7 +224,7 @@ void init_webserver() {
return request->requestAuthentication();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
datalayer.battery.info.max_discharge_amp_dA = static_cast<uint16_t>(value.toFloat() * 10);
datalayer.battery.settings.max_user_set_discharge_dA = static_cast<uint16_t>(value.toFloat() * 10);
storeSettings();
request->send(200, "text/plain", "Updated successfully");
} else {
@ -295,7 +295,7 @@ void init_webserver() {
String value = request->getParam("value")->value();
float val = value.toFloat();
if (!(val <= datalayer.battery.info.max_charge_amp_dA && val <= CHARGER_MAX_A)) {
if (!(val <= datalayer.battery.settings.max_user_set_charge_dA && val <= CHARGER_MAX_A)) {
request->send(400, "text/plain", "Bad Request");
}
@ -425,6 +425,9 @@ String get_firmware_info_processor(const String& var) {
#ifdef HW_STARK
doc["hardware"] = "Stark CMR Module";
#endif // HW_STARK
#ifdef HW_3LB
doc["hardware"] = "3LB board";
#endif // HW_STARK
doc["firmware"] = String(version_number);
serializeJson(doc, content);
@ -493,111 +496,16 @@ String processor(const String& var) {
// Display which components are used
content += "<h4 style='color: white;'>Inverter protocol: ";
#ifdef BYD_CAN
content += "BYD Battery-Box Premium HVS over CAN Bus";
#endif // BYD_CAN
#ifdef BYD_MODBUS
content += "BYD 11kWh HVM battery over Modbus RTU";
#endif // BYD_MODBUS
#ifdef 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 += datalayer.system.info.inverter_protocol;
content += "</h4>";
content += "<h4 style='color: white;'>Battery protocol: ";
#ifdef BMW_I3_BATTERY
content += "BMW i3";
#endif // BMW_I3_BATTERY
#ifdef BYD_ATTO_3_BATTERY
content += "BYD Atto 3";
#endif // BYD_ATTO_3_BATTERY
#ifdef 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 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
content += datalayer.system.info.battery_protocol;
#ifdef DOUBLE_BATTERY
content += " (Double battery)";
#endif // DOUBLE_BATTERY
if (datalayer.battery.info.chemistry == battery_chemistry_enum::LFP) {
content += " (LFP)";
}
#endif // DOUBLE_BATTERY
content += "</h4>";
#if defined CHEVYVOLT_CHARGER || defined NISSANLEAF_CHARGER
@ -659,6 +567,10 @@ String processor(const String& var) {
float powerFloat = static_cast<float>(datalayer.battery.status.active_power_W); // Convert to float
float tempMaxFloat = static_cast<float>(datalayer.battery.status.temperature_max_dC) / 10.0; // Convert to float
float tempMinFloat = static_cast<float>(datalayer.battery.status.temperature_min_dC) / 10.0; // Convert to float
float maxCurrentChargeFloat =
static_cast<float>(datalayer.battery.status.max_charge_current_dA) / 10.0; // Convert to float
float maxCurrentDischargeFloat =
static_cast<float>(datalayer.battery.status.max_discharge_current_dA) / 10.0; // Convert to float
uint16_t cell_delta_mv =
datalayer.battery.status.cell_max_voltage_mV - datalayer.battery.status.cell_min_voltage_mV;
@ -676,9 +588,13 @@ String processor(const String& var) {
if (emulator_pause_status == NORMAL) {
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 charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red");
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
}
content += "<h4>Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";

View file

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

View file

@ -72,31 +72,10 @@ CAN_frame AFORE_35A = {.FD = false,
.DLC = 8,
.ID = 0x35A,
.data = {0x65, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72}}; // Emulator
static int16_t max_charge_current_dA = 0;
static int16_t max_discharge_current_dA = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//There are more mappings that could be added, but this should be enough to use as a starting point
// Note we map both 0 and 1 messages
if (datalayer.battery.status.voltage_dV > 10) { //div0 safeguard
max_charge_current_dA = (datalayer.battery.status.max_charge_power_W * 100) / datalayer.battery.status.voltage_dV;
if (max_charge_current_dA > datalayer.battery.info.max_charge_amp_dA) {
max_charge_current_dA =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
max_discharge_current_dA =
(datalayer.battery.status.max_discharge_power_W * 100) / datalayer.battery.status.voltage_dV;
if (max_discharge_current_dA > datalayer.battery.info.max_discharge_amp_dA) {
max_discharge_current_dA =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
} else {
max_charge_current_dA = 0;
max_discharge_current_dA = 0;
}
/*0x350 Operation Information*/
AFORE_350.data.u8[0] = (datalayer.battery.status.voltage_dV & 0x00FF);
AFORE_350.data.u8[1] = (datalayer.battery.status.voltage_dV >> 8);
@ -115,11 +94,11 @@ void update_values_can_inverter() { //This function maps all the values fetched
AFORE_351.data.u8[2] = SOCMAX;
AFORE_351.data.u8[3] = SOCMIN;
AFORE_351.data.u8[4] = 0x03; //Bit0 and Bit1 set
if ((max_charge_current_dA == 0) || (datalayer.battery.status.reported_soc == 10000) ||
if ((datalayer.battery.status.max_charge_current_dA == 0) || (datalayer.battery.status.reported_soc == 10000) ||
(datalayer.battery.status.bms_status == FAULT)) {
AFORE_351.data.u8[4] &= ~0x01; // Remove Bit0 (clear) Charge enable flag
}
if ((max_discharge_current_dA == 0) || (datalayer.battery.status.reported_soc == 0) ||
if ((datalayer.battery.status.max_discharge_current_dA == 0) || (datalayer.battery.status.reported_soc == 0) ||
(datalayer.battery.status.bms_status == FAULT)) {
AFORE_351.data.u8[4] &= ~0x02; // Remove Bit1 (clear) Discharge enable flag
}
@ -135,10 +114,10 @@ void update_values_can_inverter() { //This function maps all the values fetched
AFORE_351.data.u8[7] = (datalayer.battery.info.number_of_cells >> 8);
/*0x352 - Protection parameters*/
AFORE_352.data.u8[0] = (max_charge_current_dA & 0x00FF);
AFORE_352.data.u8[1] = (max_charge_current_dA >> 8);
AFORE_352.data.u8[2] = (max_discharge_current_dA & 0x00FF);
AFORE_352.data.u8[3] = (max_discharge_current_dA >> 8);
AFORE_352.data.u8[0] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
AFORE_352.data.u8[1] = (datalayer.battery.status.max_charge_current_dA >> 8);
AFORE_352.data.u8[2] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
AFORE_352.data.u8[3] = (datalayer.battery.status.max_discharge_current_dA >> 8);
AFORE_352.data.u8[4] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
AFORE_352.data.u8[5] = (datalayer.battery.info.max_design_voltage_dV >> 8);
AFORE_352.data.u8[6] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
@ -254,4 +233,8 @@ void send_can_inverter() {
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

View file

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

View file

@ -7,21 +7,14 @@
static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
static unsigned long previousMillis60s = 0; // will store last time a 60s CAN Message was send
static uint8_t char1_151 = 0;
static uint8_t char2_151 = 0;
static uint8_t char3_151 = 0;
static uint8_t char4_151 = 0;
static uint8_t char5_151 = 0;
static uint8_t char6_151 = 0;
static uint8_t char7_151 = 0;
CAN_frame BYD_250 = {
.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x250,
.data = {0x03, 0x16, 0x00, 0x66, (uint8_t)((BATTERY_WH_MAX / 100) >> 8), (uint8_t)(BATTERY_WH_MAX / 100), 0x02,
0x09}}; //3.16 FW , Capacity kWh byte4&5 (example 24kWh = 240)
CAN_frame BYD_250 = {.FD = false,
.ext_ID = false,
.DLC = 8,
.ID = 0x250,
.data = {FW_MAJOR_VERSION, FW_MINOR_VERSION, 0x00, 0x66, (uint8_t)((BATTERY_WH_MAX / 100) >> 8),
(uint8_t)(BATTERY_WH_MAX / 100), 0x02,
0x09}}; //0-1 FW version , Capacity kWh byte4&5 (example 24kWh = 240)
CAN_frame BYD_290 = {.FD = false,
.ext_ID = false,
.DLC = 8,
@ -79,11 +72,12 @@ CAN_frame BYD_210 = {.FD = false,
.ID = 0x210,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static uint16_t discharge_current = 0;
static uint16_t charge_current = 0;
static uint8_t inverter_name[7] = {0};
static int16_t temperature_average = 0;
static uint16_t inverter_voltage = 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 fully_charged_capacity_ah = 0;
static long inverter_timestamp = 0;
@ -91,32 +85,6 @@ static bool initialDataSent = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
/* Calculate allowed charge/discharge currents*/
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
charge_current =
((datalayer.battery.status.max_charge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
//The above calculation results in (30 000*10)/3700=81A
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
discharge_current =
((datalayer.battery.status.max_discharge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
//The above calculation results in (30 000*10)/3700=81A
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
}
/* Restrict values from user settings if needed*/
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
charge_current =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
discharge_current =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
/* Calculate temperature */
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -137,11 +105,11 @@ void update_values_can_inverter() { //This function maps all the values fetched
BYD_110.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
BYD_110.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
//Maximum discharge power allowed (Unit: A+1)
BYD_110.data.u8[4] = (discharge_current >> 8);
BYD_110.data.u8[5] = (discharge_current & 0x00FF);
BYD_110.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
BYD_110.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Maximum charge power allowed (Unit: A+1)
BYD_110.data.u8[6] = (charge_current >> 8);
BYD_110.data.u8[7] = (charge_current & 0x00FF);
BYD_110.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8);
BYD_110.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
//SOC (100.00%)
BYD_150.data.u8[0] = (datalayer.battery.status.reported_soc >> 8);
@ -174,15 +142,12 @@ void update_values_can_inverter() { //This function maps all the values fetched
BYD_210.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
#ifdef DEBUG_VIA_USB
if (char1_151 != 0) {
if (inverter_name[0] != 0) {
Serial.print("Detected inverter: ");
Serial.print((char)char1_151);
Serial.print((char)char2_151);
Serial.print((char)char3_151);
Serial.print((char)char4_151);
Serial.print((char)char5_151);
Serial.print((char)char6_151);
Serial.println((char)char7_151);
for (uint8_t i = 0; i < 7; i++) {
Serial.print((char)inverter_name[i]);
}
Serial.println();
}
#endif
}
@ -194,27 +159,25 @@ void receive_can_inverter(CAN_frame rx_frame) {
if (rx_frame.data.u8[0] & 0x01) { //Battery requests identification
send_intial_data();
} else { // We can identify what inverter type we are connected to
char1_151 = rx_frame.data.u8[1];
char2_151 = rx_frame.data.u8[2];
char3_151 = rx_frame.data.u8[3];
char4_151 = rx_frame.data.u8[4];
char5_151 = rx_frame.data.u8[5];
char6_151 = rx_frame.data.u8[6];
char7_151 = rx_frame.data.u8[7];
for (uint8_t i = 0; i < 7; i++) {
inverter_name[i] = rx_frame.data.u8[i + 1];
}
}
break;
case 0x091:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
inverter_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 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;
case 0x0D1:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
inverter_SOC = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.1;
inverter_SOC = ((rx_frame.data.u8[0] << 8) | rx_frame.data.u8[1]) * 0.1;
break;
case 0x111:
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
inverter_timestamp = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[2] << 16) | (rx_frame.data.u8[1] << 8) |
rx_frame.data.u8[0]);
inverter_timestamp = ((rx_frame.data.u8[0] << 24) | (rx_frame.data.u8[1] << 16) | (rx_frame.data.u8[2] << 8) |
rx_frame.data.u8[3]);
break;
default:
break;
@ -260,4 +223,8 @@ void send_intial_data() {
transmit_can(&BYD_3D0_2, 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

View file

@ -3,8 +3,11 @@
#include "../include.h"
#define CAN_INVERTER_SELECTED
#define FW_MAJOR_VERSION 0x03
#define FW_MINOR_VERSION 0x29
void send_intial_data();
void transmit_can(CAN_frame* tx_frame, int interface);
void setup_inverter(void);
#endif

View file

@ -65,13 +65,13 @@ void handle_update_data_modbusp301_byd() {
}
// Convert max discharge Amp value to max Watt
user_configured_max_discharge_W =
((datalayer.battery.info.max_discharge_amp_dA * datalayer.battery.info.max_design_voltage_dV) / 100);
((datalayer.battery.settings.max_user_set_discharge_dA * datalayer.battery.info.max_design_voltage_dV) / 100);
// Use the smaller value, battery reported value OR user configured value
max_discharge_W = std::min(datalayer.battery.status.max_discharge_power_W, user_configured_max_discharge_W);
// Convert max charge Amp value to max Watt
user_configured_max_charge_W =
((datalayer.battery.info.max_charge_amp_dA * datalayer.battery.info.max_design_voltage_dV) / 100);
((datalayer.battery.settings.max_user_set_charge_dA * datalayer.battery.info.max_design_voltage_dV) / 100);
// Use the smaller value, battery reported value OR user configured value
max_charge_W = std::min(datalayer.battery.status.max_charge_power_W, user_configured_max_charge_W);
@ -143,4 +143,8 @@ void verify_inverter_modbus() {
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

View file

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

View file

@ -80,30 +80,6 @@ static uint16_t ampere_hours_remaining = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//Calculate values
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
discharge_current =
((datalayer.battery.status.max_discharge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
charge_current =
((datalayer.battery.status.max_charge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
}
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
charge_current =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
discharge_current =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -118,15 +94,14 @@ void update_values_can_inverter() { //This function maps all the values fetched
SMA_358.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
SMA_358.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long)
SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >>
8); //Minvoltage behaves strange on SMA, cuts out at 56% of the set value?
SMA_358.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
//Discharge limited current, 500 = 50A, (0.1, A)
SMA_358.data.u8[4] = (discharge_current >> 8);
SMA_358.data.u8[5] = (discharge_current & 0x00FF);
SMA_358.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
SMA_358.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Charge limited current, 125 =12.5A (0.1, A)
SMA_358.data.u8[6] = (charge_current >> 8);
SMA_358.data.u8[7] = (charge_current & 0x00FF);
SMA_358.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8);
SMA_358.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
//SOC (100.00%)
SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8);
@ -276,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

View file

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

View file

@ -22,15 +22,12 @@ below that you can customize, incase you use a lower voltage battery with this p
#define TOTAL_LIFETIME_WH_ACCUMULATED 0 //We dont have this value in the emulator
/* Do not change code below unless you are sure what you are doing */
static uint16_t max_charge_rate_amp = 0;
static uint16_t max_discharge_rate_amp = 0;
static int16_t temperature_average = 0;
static uint16_t voltage_per_pack = 0;
static int16_t current_per_pack = 0;
static uint8_t temperature_max_per_pack = 0;
static uint8_t temperature_min_per_pack = 0;
static uint8_t current_pack_info = 0;
static uint8_t inverterStillAlive = 60; // Inverter can be missing for 1minute on startup
static bool send_cellvoltages = false;
static unsigned long previousMillisCellvoltage = 0; // Store the last time a cellvoltage CAN messages were sent
@ -364,70 +361,16 @@ void update_values_can_inverter() { //This function maps all the CAN values fet
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
//datalayer.battery.status.max_charge_power_W (30000W max)
if (datalayer.battery.status.reported_soc > 9999) { // 99.99%
// Additional safety incase SOC% is 100, then do not charge battery further
max_charge_rate_amp = 0;
} else { // We can pass on the battery charge rate (in W) to the inverter (that takes A)
if (datalayer.battery.status.max_charge_power_W >= 30000) {
max_charge_rate_amp = 75; // Incase battery can take over 30kW, cap value to 75A
} else { // Calculate the W value into A
if (datalayer.battery.status.voltage_dV > 10) {
max_charge_rate_amp =
datalayer.battery.status.max_charge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
} else { // We avoid dividing by 0 and crashing the board
// If we have no voltage, something has gone wrong, do not allow charging
max_charge_rate_amp = 0;
}
}
}
//datalayer.battery.status.max_discharge_power_W (30000W max)
if (datalayer.battery.status.reported_soc < 100) { // 1.00%
// Additional safety in case SOC% is below 1, then do not discharge battery further
max_discharge_rate_amp = 0;
} else { // We can pass on the battery discharge rate to the inverter
if (datalayer.battery.status.max_discharge_power_W >= 30000) {
max_discharge_rate_amp = 75; // Incase battery can be charged with over 30kW, cap value to 75A
} else { // Calculate the W value into A
if (datalayer.battery.status.voltage_dV > 10) {
max_discharge_rate_amp =
datalayer.battery.status.max_discharge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
} else { // We avoid dividing by 0 and crashing the board
// If we have no voltage, something has gone wrong, do not allow discharging
max_discharge_rate_amp = 0;
}
}
}
//Cap the value according to user settings. Some inverters cannot handle large values.
if ((max_charge_rate_amp * 10) > datalayer.battery.info.max_charge_amp_dA) {
max_charge_rate_amp = (datalayer.battery.info.max_charge_amp_dA / 10);
}
if ((max_discharge_rate_amp * 10) > datalayer.battery.info.max_discharge_amp_dA) {
max_discharge_rate_amp = (datalayer.battery.info.max_discharge_amp_dA / 10);
}
if (inverterStillAlive > 0) {
inverterStillAlive--;
}
if (!inverterStillAlive) {
set_event(EVENT_CAN_INVERTER_MISSING, 0);
} else {
clear_event(EVENT_CAN_INVERTER_MISSING);
}
//Put the values into the CAN messages
//BMS_Limits
FOXESS_1872.data.u8[0] = (uint8_t)datalayer.battery.info.max_design_voltage_dV;
FOXESS_1872.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8);
FOXESS_1872.data.u8[2] = (uint8_t)datalayer.battery.info.min_design_voltage_dV;
FOXESS_1872.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
FOXESS_1872.data.u8[4] = (uint8_t)(max_charge_rate_amp * 10);
FOXESS_1872.data.u8[5] = ((max_charge_rate_amp * 10) >> 8);
FOXESS_1872.data.u8[6] = (uint8_t)(max_discharge_rate_amp * 10);
FOXESS_1872.data.u8[7] = ((max_discharge_rate_amp * 10) >> 8);
FOXESS_1872.data.u8[4] = (uint8_t)datalayer.battery.status.max_charge_current_dA;
FOXESS_1872.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
FOXESS_1872.data.u8[6] = (uint8_t)datalayer.battery.status.max_discharge_current_dA;
FOXESS_1872.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
//BMS_PackData
FOXESS_1873.data.u8[0] = (uint8_t)datalayer.battery.status.voltage_dV; // OK
@ -463,7 +406,7 @@ void update_values_can_inverter() { //This function maps all the CAN values fet
// 0x1876 b0 bit 0 appears to be 1 when at maxsoc and BMS says charge is not allowed -
// when at 0 indicates charge is possible - additional note there is something more to it than this,
// it's not as straight forward - needs more testing to find what sets/unsets bit0 of byte0
if ((max_charge_rate_amp == 0) || (datalayer.battery.status.reported_soc == 10000) ||
if ((datalayer.battery.status.max_charge_current_dA == 0) || (datalayer.battery.status.reported_soc == 10000) ||
(datalayer.battery.status.bms_status == FAULT)) {
FOXESS_1876.data.u8[0] = 0x01;
} else { //continue using battery
@ -732,7 +675,7 @@ void send_can_inverter() { // This function loops as fast as possible
void receive_can_inverter(CAN_frame rx_frame) {
if (rx_frame.ID == 0x1871) {
inverterStillAlive = CAN_STILL_ALIVE;
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
if (rx_frame.data.u8[0] == 0x03) { //0x1871 [0x03, 0x06, 0x17, 0x05, 0x09, 0x09, 0x28, 0x22]
//This message is sent by the inverter every '6' seconds (0.5s after the pack serial numbers)
//and contains a timestamp in bytes 2-7 i.e. <YY>,<MM>,<DD>,<HH>,<mm>,<ss>
@ -794,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

View file

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

View file

@ -134,32 +134,10 @@ CAN_frame PYLON_4291 = {.FD = false,
.ID = 0x4291,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static int16_t max_charge_current = 0;
static int16_t max_discharge_current = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//There are more mappings that could be added, but this should be enough to use as a starting point
// Note we map both 0 and 1 messages
if (datalayer.battery.status.voltage_dV > 10) { //div0 safeguard
max_charge_current = (datalayer.battery.status.max_charge_power_W * 100) / datalayer.battery.status.voltage_dV;
if (max_charge_current > datalayer.battery.info.max_charge_amp_dA) {
max_charge_current =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
max_discharge_current =
(datalayer.battery.status.max_discharge_power_W * 100) / datalayer.battery.status.voltage_dV;
if (max_discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
max_discharge_current =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
} else {
max_charge_current = 0;
max_discharge_current = 0;
}
//Charge / Discharge allowed
PYLON_4280.data.u8[0] = 0;
PYLON_4280.data.u8[1] = 0;
@ -253,28 +231,28 @@ void update_values_can_inverter() { //This function maps all the values fetched
#ifdef SET_30K_OFFSET
//Max ChargeCurrent
PYLON_4220.data.u8[4] = ((max_charge_current + 30000) & 0x00FF);
PYLON_4220.data.u8[5] = ((max_charge_current + 30000) >> 8);
PYLON_4221.data.u8[4] = ((max_charge_current + 30000) & 0x00FF);
PYLON_4221.data.u8[5] = ((max_charge_current + 30000) >> 8);
PYLON_4220.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
PYLON_4220.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
PYLON_4221.data.u8[4] = ((datalayer.battery.status.max_charge_current_dA + 30000) & 0x00FF);
PYLON_4221.data.u8[5] = ((datalayer.battery.status.max_charge_current_dA + 30000) >> 8);
//Max DischargeCurrent
PYLON_4220.data.u8[6] = ((30000 - max_discharge_current) & 0x00FF);
PYLON_4220.data.u8[7] = ((30000 - max_discharge_current) >> 8);
PYLON_4221.data.u8[6] = ((30000 - max_discharge_current) & 0x00FF);
PYLON_4221.data.u8[7] = ((30000 - max_discharge_current) >> 8);
PYLON_4220.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
PYLON_4220.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
PYLON_4221.data.u8[6] = ((30000 - datalayer.battery.status.max_discharge_current_dA) & 0x00FF);
PYLON_4221.data.u8[7] = ((30000 - datalayer.battery.status.max_discharge_current_dA) >> 8);
#else
//Max ChargeCurrent
PYLON_4220.data.u8[4] = (max_charge_current & 0x00FF);
PYLON_4220.data.u8[5] = (max_charge_current >> 8);
PYLON_4221.data.u8[4] = (max_charge_current & 0x00FF);
PYLON_4221.data.u8[5] = (max_charge_current >> 8);
PYLON_4220.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
PYLON_4220.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
PYLON_4221.data.u8[4] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
PYLON_4221.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
//Max DishargeCurrent
PYLON_4220.data.u8[6] = (max_discharge_current & 0x00FF);
PYLON_4220.data.u8[7] = (max_discharge_current >> 8);
PYLON_4221.data.u8[6] = (max_discharge_current & 0x00FF);
PYLON_4221.data.u8[7] = (max_discharge_current >> 8);
PYLON_4220.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
PYLON_4220.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
PYLON_4221.data.u8[6] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
PYLON_4221.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
#endif
//Max cell voltage
@ -499,4 +477,8 @@ void send_system_data() { //System equipment information
transmit_can(&PYLON_4291, can_config.inverter);
#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

View file

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

View file

@ -42,20 +42,13 @@ CAN_frame PYLON_35E = {.FD = false,
void update_values_can_inverter() {
// This function maps all the values fetched from battery CAN to the correct CAN messages
// do not update values unless we have some voltage, as we will run into IntegerDivideByZero exceptions otherwise
if (datalayer.battery.status.voltage_dV == 0)
return;
// TODO: officially this value is "battery charge voltage". Do we need to add something here to the actual voltage?
PYLON_351.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff;
PYLON_351.data.u8[1] = datalayer.battery.status.voltage_dV >> 8;
int16_t maxChargeCurrent = datalayer.battery.status.max_charge_power_W * 100 / datalayer.battery.status.voltage_dV;
PYLON_351.data.u8[2] = maxChargeCurrent & 0xff;
PYLON_351.data.u8[3] = maxChargeCurrent >> 8;
int16_t maxDischargeCurrent =
datalayer.battery.status.max_discharge_power_W * 100 / datalayer.battery.status.voltage_dV;
PYLON_351.data.u8[4] = maxDischargeCurrent & 0xff;
PYLON_351.data.u8[5] = maxDischargeCurrent >> 8;
PYLON_351.data.u8[2] = datalayer.battery.status.max_charge_current_dA & 0xff;
PYLON_351.data.u8[3] = datalayer.battery.status.max_charge_current_dA >> 8;
PYLON_351.data.u8[4] = datalayer.battery.status.max_discharge_current_dA & 0xff;
PYLON_351.data.u8[5] = datalayer.battery.status.max_discharge_current_dA >> 8;
PYLON_355.data.u8[0] = (datalayer.battery.status.reported_soc / 10) & 0xff;
PYLON_355.data.u8[1] = (datalayer.battery.status.reported_soc / 10) >> 8;
@ -75,11 +68,11 @@ void update_values_can_inverter() {
PYLON_359.data.u8[2] = 0x00;
PYLON_359.data.u8[3] = 0x00;
PYLON_359.data.u8[4] = PACK_NUMBER;
PYLON_359.data.u8[5] = 'P';
PYLON_359.data.u8[6] = 'N';
PYLON_359.data.u8[5] = 0x50; //P
PYLON_359.data.u8[6] = 0x4E; //N
// ERRORS
if (datalayer.battery.status.current_dA >= maxDischargeCurrent)
if (datalayer.battery.status.current_dA >= (datalayer.battery.status.max_discharge_current_dA + 10))
PYLON_359.data.u8[0] |= 0x80;
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE)
PYLON_359.data.u8[0] |= 0x10;
@ -88,11 +81,11 @@ void update_values_can_inverter() {
if (datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV)
PYLON_359.data.u8[0] |= 0x04;
// we never set PYLON_359.data.u8[1] |= 0x80 called "BMS internal"
if (datalayer.battery.status.current_dA <= -1 * maxChargeCurrent)
if (datalayer.battery.status.current_dA <= -1 * datalayer.battery.status.max_charge_current_dA)
PYLON_359.data.u8[1] |= 0x01;
// WARNINGS (using same rules as errors but reporting earlier)
if (datalayer.battery.status.current_dA >= maxDischargeCurrent * WARNINGS_PERCENT / 100)
if (datalayer.battery.status.current_dA >= datalayer.battery.status.max_discharge_current_dA * WARNINGS_PERCENT / 100)
PYLON_359.data.u8[2] |= 0x80;
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE * WARNINGS_PERCENT / 100)
PYLON_359.data.u8[2] |= 0x10;
@ -101,7 +94,8 @@ void update_values_can_inverter() {
if (datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV + 100)
PYLON_359.data.u8[2] |= 0x04;
// we never set PYLON_359.data.u8[3] |= 0x80 called "BMS internal"
if (datalayer.battery.status.current_dA <= -1 * maxChargeCurrent * WARNINGS_PERCENT / 100)
if (datalayer.battery.status.current_dA <=
-1 * datalayer.battery.status.max_charge_current_dA * WARNINGS_PERCENT / 100)
PYLON_359.data.u8[3] |= 0x01;
PYLON_35C.data.u8[0] = 0xC0; // enable charging and discharging
@ -139,4 +133,8 @@ void send_can_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

View file

@ -12,5 +12,6 @@
void send_system_data();
void send_setup_info();
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;
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;
dataLinkTransmit.updateData(0, datalayer.battery.status.real_soc);
dataLinkTransmit.updateData(1, datalayer.battery.status.soh_pptt);

View file

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

View file

@ -70,37 +70,12 @@ CAN_frame SMA_158 = {.FD = false,
.ID = 0x158,
.data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}};
static int16_t discharge_current = 0;
static int16_t charge_current = 0;
static int16_t temperature_average = 0;
static uint16_t ampere_hours_remaining = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//Calculate values
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
discharge_current =
((datalayer.battery.status.max_discharge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
charge_current =
((datalayer.battery.status.max_charge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
}
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
charge_current =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
discharge_current =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -119,11 +94,11 @@ void update_values_can_inverter() { //This function maps all the values fetched
8); //Minvoltage behaves strange on SMA, cuts out at 56% of the set value?
SMA_358.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
//Discharge limited current, 500 = 50A, (0.1, A)
SMA_358.data.u8[4] = (discharge_current >> 8);
SMA_358.data.u8[5] = (discharge_current & 0x00FF);
SMA_358.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
SMA_358.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Charge limited current, 125 =12.5A (0.1, A)
SMA_358.data.u8[6] = (charge_current >> 8);
SMA_358.data.u8[7] = (charge_current & 0x00FF);
SMA_358.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8);
SMA_358.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
//SOC (100.00%)
SMA_3D8.data.u8[0] = (datalayer.battery.status.reported_soc >> 8);
@ -274,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

View file

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

View file

@ -81,8 +81,6 @@ CAN_frame SMA_018 = {.FD = false,
.ID = 0x018,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static uint16_t discharge_current = 0;
static uint16_t charge_current = 0;
static int16_t temperature_average = 0;
static uint16_t ampere_hours_remaining = 0;
static uint16_t ampere_hours_max = 0;
@ -123,29 +121,6 @@ InvInitState invInitState = SYSTEM_FREQUENCY;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the inverter CAN
//Calculate values
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
charge_current =
((datalayer.battery.status.max_charge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
discharge_current =
((datalayer.battery.status.max_discharge_power_W * 10) /
datalayer.battery.status.voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
}
if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
charge_current =
datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
discharge_current =
datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
@ -167,11 +142,11 @@ void update_values_can_inverter() { //This function maps all the values fetched
SMA_00D.data.u8[2] = (datalayer.battery.info.min_design_voltage_dV >> 8);
SMA_00D.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
//Discharge limited current, 500 = 50A, (0.1, A)
SMA_00D.data.u8[4] = (discharge_current >> 8);
SMA_00D.data.u8[5] = (discharge_current & 0x00FF);
SMA_00D.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
SMA_00D.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Charge limited current, 125 =12.5A (0.1, A)
SMA_00D.data.u8[6] = (charge_current >> 8);
SMA_00D.data.u8[7] = (charge_current & 0x00FF);
SMA_00D.data.u8[6] = (datalayer.battery.status.max_charge_current_dA >> 8);
SMA_00D.data.u8[7] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
// Battery State
//SOC (100.00%)
@ -345,4 +320,9 @@ void send_tripower_init() {
transmit_can(&SMA_017, can_config.inverter); // Battery Manufacturer
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

View file

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

View file

@ -207,10 +207,10 @@ void update_values_can_inverter() { //This function maps all the values fetched
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage
SOFAR_351.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
SOFAR_351.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV & 0x00FF);
//SOFAR_351.data.u8[2] = DC charge current limitation (Default 25.0A)
//SOFAR_351.data.u8[3] = DC charge current limitation
//SOFAR_351.data.u8[4] = DC discharge current limitation (Default 25.0A)
//SOFAR_351.data.u8[5] = DC discharge current limitation
SOFAR_351.data.u8[2] = (datalayer.battery.status.max_charge_current_dA >> 8);
SOFAR_351.data.u8[3] = (datalayer.battery.status.max_charge_current_dA & 0x00FF);
SOFAR_351.data.u8[4] = (datalayer.battery.status.max_discharge_current_dA >> 8);
SOFAR_351.data.u8[5] = (datalayer.battery.status.max_discharge_current_dA & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long) Discharge Cutoff Voltage
SOFAR_351.data.u8[6] = (datalayer.battery.info.min_design_voltage_dV >> 8);
SOFAR_351.data.u8[7] = (datalayer.battery.info.min_design_voltage_dV & 0x00FF);
@ -263,4 +263,9 @@ void send_can_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

View file

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

View file

@ -10,8 +10,6 @@
// https://github.com/dalathegreat/Battery-Emulator/wiki/Solax-inverters
/* Do not change code below unless you are sure what you are doing */
static uint16_t max_charge_rate_amp = 0;
static uint16_t max_discharge_rate_amp = 0;
static int16_t temperature_average = 0;
static uint8_t STATE = BATTERY_ANNOUNCE;
static unsigned long LastFrameTime = 0;
@ -93,50 +91,6 @@ void update_values_can_inverter() { //This function maps all the values fetched
temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
//datalayer.battery.status.max_charge_power_W (30000W max)
if (datalayer.battery.status.reported_soc > 9999) { // 99.99%
// Additional safety incase SOC% is 100, then do not charge battery further
max_charge_rate_amp = 0;
} else { // We can pass on the battery charge rate (in W) to the inverter (that takes A)
if (datalayer.battery.status.max_charge_power_W >= 30000) {
max_charge_rate_amp = 75; // Incase battery can take over 30kW, cap value to 75A
} else { // Calculate the W value into A
if (datalayer.battery.status.voltage_dV > 10) {
max_charge_rate_amp =
datalayer.battery.status.max_charge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
} else { // We avoid dividing by 0 and crashing the board
// If we have no voltage, something has gone wrong, do not allow charging
max_charge_rate_amp = 0;
}
}
}
//datalayer.battery.status.max_discharge_power_W (30000W max)
if (datalayer.battery.status.reported_soc < 100) { // 1.00%
// Additional safety in case SOC% is below 1, then do not discharge battery further
max_discharge_rate_amp = 0;
} else { // We can pass on the battery discharge rate to the inverter
if (datalayer.battery.status.max_discharge_power_W >= 30000) {
max_discharge_rate_amp = 75; // Incase battery can be charged with over 30kW, cap value to 75A
} else { // Calculate the W value into A
if (datalayer.battery.status.voltage_dV > 10) {
max_discharge_rate_amp =
datalayer.battery.status.max_discharge_power_W / (datalayer.battery.status.voltage_dV * 0.1); // P/U=I
} else { // We avoid dividing by 0 and crashing the board
// If we have no voltage, something has gone wrong, do not allow discharging
max_discharge_rate_amp = 0;
}
}
}
//Cap the value according to user settings. Some inverters cannot handle large values.
if ((max_charge_rate_amp * 10) > datalayer.battery.info.max_charge_amp_dA) {
max_charge_rate_amp = (datalayer.battery.info.max_charge_amp_dA / 10);
}
if ((max_discharge_rate_amp * 10) > datalayer.battery.info.max_discharge_amp_dA) {
max_discharge_rate_amp = (datalayer.battery.info.max_discharge_amp_dA / 10);
}
// Batteries might be larger than uint16_t value can take
if (datalayer.battery.info.total_capacity_Wh > 65000) {
capped_capacity_Wh = 65000;
@ -156,10 +110,10 @@ void update_values_can_inverter() { //This function maps all the values fetched
SOLAX_1872.data.u8[1] = (datalayer.battery.info.max_design_voltage_dV >> 8);
SOLAX_1872.data.u8[2] = (uint8_t)datalayer.battery.info.min_design_voltage_dV;
SOLAX_1872.data.u8[3] = (datalayer.battery.info.min_design_voltage_dV >> 8);
SOLAX_1872.data.u8[4] = (uint8_t)(max_charge_rate_amp * 10);
SOLAX_1872.data.u8[5] = ((max_charge_rate_amp * 10) >> 8);
SOLAX_1872.data.u8[6] = (uint8_t)(max_discharge_rate_amp * 10);
SOLAX_1872.data.u8[7] = ((max_discharge_rate_amp * 10) >> 8);
SOLAX_1872.data.u8[4] = (uint8_t)datalayer.battery.status.max_charge_current_dA;
SOLAX_1872.data.u8[5] = (datalayer.battery.status.max_charge_current_dA >> 8);
SOLAX_1872.data.u8[6] = (uint8_t)datalayer.battery.status.max_discharge_current_dA;
SOLAX_1872.data.u8[7] = (datalayer.battery.status.max_discharge_current_dA >> 8);
//BMS_PackData
SOLAX_1873.data.u8[0] = (uint8_t)datalayer.battery.status.voltage_dV; // OK
@ -298,4 +252,9 @@ void receive_can_inverter(CAN_frame rx_frame) {
#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

View file

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