mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 09:49:32 +02:00
Merge branch 'main' into feature/kostal-rs485
This commit is contained in:
commit
504c6b3d7a
74 changed files with 2835 additions and 931 deletions
13
.github/workflows/compile-all-batteries.yml
vendored
13
.github/workflows/compile-all-batteries.yml
vendored
|
@ -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
|
||||
|
||||
|
|
3
.github/workflows/compile-all-inverters.yml
vendored
3
.github/workflows/compile-all-inverters.yml
vendored
|
@ -42,11 +42,14 @@ 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
|
||||
- SCHNEIDER_CAN
|
||||
- SMA_CAN
|
||||
- SMA_TRIPOWER_CAN
|
||||
- SOFAR_CAN
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
Preferences settings; // Store user settings
|
||||
// The current software version, shown on webserver
|
||||
const char* version_number = "7.7.dev";
|
||||
const char* version_number = "7.8.dev";
|
||||
|
||||
// Interval settings
|
||||
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
|
||||
|
@ -70,13 +70,11 @@ volatile bool send_ok = 0;
|
|||
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
|
||||
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
|
||||
|
@ -296,7 +293,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
|
||||
|
@ -404,11 +401,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
|
||||
|
@ -559,29 +556,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() {
|
||||
|
@ -619,31 +593,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;
|
||||
|
@ -842,7 +817,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
|
||||
*
|
||||
|
@ -865,6 +859,8 @@ void update_scaled_values() {
|
|||
* Before we use real_soc, we must make sure that it's within the range of min_percentage and max_percentage.
|
||||
*/
|
||||
uint32_t calc_soc;
|
||||
uint32_t calc_max_capacity;
|
||||
uint32_t calc_reserved_capacity;
|
||||
// Make sure that the SOC starts out between min and max percentages
|
||||
calc_soc = CONSTRAIN(datalayer.battery.status.real_soc, datalayer.battery.settings.min_percentage,
|
||||
datalayer.battery.settings.max_percentage);
|
||||
|
@ -875,8 +871,6 @@ void update_scaled_values() {
|
|||
|
||||
// Calculate the scaled remaining capacity in Wh
|
||||
if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {
|
||||
uint32_t calc_max_capacity;
|
||||
uint32_t calc_reserved_capacity;
|
||||
calc_max_capacity = (datalayer.battery.status.remaining_capacity_Wh * 10000 / datalayer.battery.status.real_soc);
|
||||
calc_reserved_capacity = calc_max_capacity * datalayer.battery.settings.min_percentage / 10000;
|
||||
// remove % capacity reserved in min_percentage to total_capacity_Wh
|
||||
|
@ -886,9 +880,31 @@ void update_scaled_values() {
|
|||
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
|
||||
}
|
||||
|
||||
} else { // No SOC window wanted. Set scaled to same as real.
|
||||
#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) {
|
||||
calc_max_capacity =
|
||||
(datalayer.battery2.status.remaining_capacity_Wh * 10000 / datalayer.battery2.status.real_soc);
|
||||
calc_reserved_capacity = calc_max_capacity * datalayer.battery2.settings.min_percentage / 10000;
|
||||
// remove % capacity reserved in min_percentage to total_capacity_Wh
|
||||
datalayer.battery2.status.reported_remaining_capacity_Wh =
|
||||
datalayer.battery2.status.remaining_capacity_Wh - calc_reserved_capacity;
|
||||
} else {
|
||||
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
|
||||
}
|
||||
#endif
|
||||
|
||||
} 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
|
||||
datalayer.battery2.status.reported_soc = datalayer.battery2.status.real_soc;
|
||||
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
|
||||
#endif
|
||||
}
|
||||
#ifdef DOUBLE_BATTERY
|
||||
// Perform extra SOC sanity checks on double battery setups
|
||||
|
@ -964,8 +980,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();
|
||||
}
|
||||
|
@ -1051,6 +1067,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:
|
||||
|
@ -1093,10 +1112,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);
|
||||
|
@ -1109,8 +1124,15 @@ 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);
|
||||
#ifdef CHADEMO_BATTERY
|
||||
ISA_handleFrame(rx_frame);
|
||||
#endif
|
||||
}
|
||||
if (interface == can_config.inverter) {
|
||||
#ifdef CAN_INVERTER_SELECTED
|
||||
|
|
|
@ -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
|
||||
|
@ -42,6 +44,7 @@
|
|||
//#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus
|
||||
//#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus
|
||||
//#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus
|
||||
//#define SCHNEIDER_CAN //Enable this line to emulate a "Schneider Version 2: SE BMS" over CAN bus
|
||||
//#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus
|
||||
//#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus
|
||||
//#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus
|
||||
|
@ -50,10 +53,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.
|
||||
|
|
|
@ -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
|
||||
|
@ -16,6 +20,7 @@
|
|||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
#include "CHADEMO-BATTERY.h"
|
||||
#include "CHADEMO-SHUNTS.h"
|
||||
#endif
|
||||
|
||||
#ifdef IMIEV_CZERO_ION_BATTERY
|
||||
|
@ -54,6 +59,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
|
||||
|
|
|
@ -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
|
||||
|
@ -422,10 +416,10 @@ void update_values_battery2() { //This function maps all the values fetched via
|
|||
} else {
|
||||
clear_event(EVENT_HVIL_FAILURE);
|
||||
}
|
||||
if (battery2_status_precharge_locked == 2) { // Capacitor seated?
|
||||
set_event(EVENT_PRECHARGE_FAILURE, 2);
|
||||
if (battery2_status_error_disconnecting_switch > 0) { // Check if contactors are sticking / welded
|
||||
set_event(EVENT_CONTACTOR_WELDED, 0);
|
||||
} else {
|
||||
clear_event(EVENT_PRECHARGE_FAILURE);
|
||||
clear_event(EVENT_CONTACTOR_WELDED);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -490,10 +480,10 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
} else {
|
||||
clear_event(EVENT_HVIL_FAILURE);
|
||||
}
|
||||
if (battery_status_precharge_locked == 2) { // Capacitor seated?
|
||||
set_event(EVENT_PRECHARGE_FAILURE, 0);
|
||||
if (battery_status_error_disconnecting_switch > 0) { // Check if contactors are sticking / welded
|
||||
set_event(EVENT_CONTACTOR_WELDED, 0);
|
||||
} else {
|
||||
clear_event(EVENT_PRECHARGE_FAILURE);
|
||||
clear_event(EVENT_CONTACTOR_WELDED);
|
||||
}
|
||||
|
||||
// Update webserver datalayer
|
||||
|
@ -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;
|
||||
|
|
794
Software/src/battery/BMW-IX-BATTERY.cpp
Normal file
794
Software/src/battery/BMW-IX-BATTERY.cpp
Normal file
|
@ -0,0 +1,794 @@
|
|||
#include "../include.h"
|
||||
#ifdef BMW_IX_BATTERY
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "../datalayer/datalayer_extended.h"
|
||||
#include "../devboard/utils/events.h"
|
||||
#include "BMW-IX-BATTERY.h"
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis20 = 0; // will store last time a 20ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was send
|
||||
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
|
||||
static unsigned long previousMillis640 = 0; // will store last time a 600ms CAN Message was send
|
||||
static unsigned long previousMillis1000 = 0; // will store last time a 1000ms CAN Message was send
|
||||
static unsigned long previousMillis5000 = 0; // will store last time a 5000ms CAN Message was send
|
||||
static unsigned long previousMillis10000 = 0; // will store last time a 10000ms CAN Message was send
|
||||
|
||||
#define ALIVE_MAX_VALUE 14 // BMW CAN messages contain alive counter, goes from 0...14
|
||||
|
||||
enum CmdState { SOH, CELL_VOLTAGE_MINMAX, SOC, CELL_VOLTAGE_CELLNO, CELL_VOLTAGE_CELLNO_LAST };
|
||||
|
||||
static CmdState cmdState = SOC;
|
||||
|
||||
/*
|
||||
Suspected Vehicle comms required:
|
||||
0x06D DLC? 1000ms - counters?
|
||||
0x2F1 DLC? 1000ms during run : 0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF - at startup 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF3, 0xFF. Suspect byte [4] is a counter
|
||||
0x439 DLC4 1000ms STATIC
|
||||
0x0C0 DLC2 200ms needs counter
|
||||
0x587 DLC8 appears at startup 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF , 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF, 0x78 0x07 0x00 0x00 0xFF 0xFF 0xFF 0xFF, 0x06 0x00 0x00 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x82 0xFF 0xFF 0xFF 0xFF 0xFF, 0x01 0x03 0x80 0xFF 0xFF 0xFF 0xFF 0xFF
|
||||
|
||||
SME Output:
|
||||
0x08F DLC48 10ms - Appears to have analog readings like volt/temp/current
|
||||
0x12B8D087 5000ms - Extended ID
|
||||
0x1D2 DLC8 1000ms
|
||||
0x20B DLC8 1000ms
|
||||
0x2E2 DLC16 1000ms
|
||||
0x2F1 DLC8 1000ms
|
||||
0x31F DLC16 100ms - 2 downward counters?
|
||||
0x453 DLC20 200ms
|
||||
0x486 DLC48 1000ms
|
||||
0x49C DLC8 1000ms
|
||||
0x4A1 DLC8 1000ms
|
||||
0x4BB DLC64 200ms - seems multplexed on [0]
|
||||
0x4D0 DLC64 1000ms - some slow/flickering values - possible change during fault
|
||||
0x510 DLC8 100ms STATIC 40 10 40 00 6F DF 19 00 during run - Startup sends this once: 0x40 0x10 0x02 0x00 0x00 0x00 0x00 0x00
|
||||
0x607 UDS Response
|
||||
|
||||
No vehicle log available, SME asks for:
|
||||
0x125 (CCU)
|
||||
0x16E (CCU)
|
||||
0x340 (CCU)
|
||||
0x4F8 (CCU)
|
||||
0x188 (CCU)
|
||||
0x91 (EME1)
|
||||
0xAA (EME2)
|
||||
0x?? Suspect there is a drive mode flag somewhere - balancing might only be active in some modes
|
||||
|
||||
TODO
|
||||
- Request batt serial number on F1 8C (already parsing RX)
|
||||
|
||||
*/
|
||||
|
||||
//Vehicle CAN START
|
||||
CAN_frame BMWiX_06D = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x06D,
|
||||
.data = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0xFF}}; // 1000ms BDC Output - [0] static [1,2][3,4] counter x2. 3,4 is 9 higher than 1,2 is needed? [5-7] static
|
||||
|
||||
CAN_frame BMWiX_0C0 = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 2,
|
||||
.ID = 0x0C0,
|
||||
.data = {
|
||||
0xF0,
|
||||
0x08}}; // Keep Alive 2 BDC>SME 200ms First byte cycles F0 > FE second byte 08 static - MINIMUM ID TO KEEP SME AWAKE
|
||||
|
||||
CAN_frame BMWiX_276 = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x476,
|
||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFC}}; // 5000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED
|
||||
|
||||
CAN_frame BMWiX_2F1 = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x2F1,
|
||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0x9B, 0x00, 0xF3, 0xFF}}; // 1000ms BDC Output - Static values - varies at startup
|
||||
|
||||
CAN_frame BMWiX_439 = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 4,
|
||||
.ID = 0x439,
|
||||
.data = {0xFF, 0xBF, 0xFF, 0xFF}}; // 1000ms BDC Output - Static values
|
||||
|
||||
CAN_frame
|
||||
BMWiX_486 =
|
||||
{
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 48,
|
||||
.ID = 0x486,
|
||||
.data =
|
||||
{
|
||||
0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
|
||||
0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF,
|
||||
0xFE, 0xFF, 0xFF, 0x7F, 0x33, 0xFD, 0xFD, 0xFD, 0xFD, 0xC0, 0x41, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED
|
||||
|
||||
CAN_frame BMWiX_49C = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 4,
|
||||
.ID = 0x49C,
|
||||
.data = {0xD2, 0xF2, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF}}; // 1000ms BDC Output - Suspected keep alive Static CONFIRM NEEDED
|
||||
|
||||
CAN_frame BMWiX_510 = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x510,
|
||||
.data = {0x40, 0x10, 0x40, 0x00, 0x6F, 0xDF, 0x19, 0x00}}; // 100ms BDC Output - Static values
|
||||
|
||||
CAN_frame BMWiX_12B8D087 = {.FD = true,
|
||||
.ext_ID = true,
|
||||
.DLC = 2,
|
||||
.ID = 0x12B8D087,
|
||||
.data = {0xFC, 0xFF}}; // 5000ms SME Output - Static values
|
||||
//Vehicle CAN END
|
||||
|
||||
//Request Data CAN START
|
||||
CAN_frame BMWiX_6F4 = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; // Generic UDS Request data from SME. byte 4 selects requested value
|
||||
CAN_frame BMWiX_6F4_REQUEST_SLEEPMODE = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 4,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x02, 0x11, 0x04}}; // UDS Request Request BMS/SME goes to Sleep Mode
|
||||
CAN_frame BMWiX_6F4_REQUEST_HARD_RESET = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 4,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x02, 0x11, 0x01}}; // UDS Request Hard reset of BMS/SME
|
||||
CAN_frame BMWiX_6F4_REQUEST_CELL_TEMP = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xDD, 0xC0}}; // UDS Request Cell Temperatures
|
||||
CAN_frame BMWiX_6F4_REQUEST_SOC = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0xCE}}; // Min/Avg/Max SOC%
|
||||
CAN_frame BMWiX_6F4_REQUEST_CAPACITY = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0xC7}}; //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias
|
||||
CAN_frame BMWiX_6F4_REQUEST_MINMAXCELLV = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x53}}; //Min and max cell voltage 10V = Qualifier Invalid
|
||||
CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x4A}}; //Main Battery Voltage (After Contactor)
|
||||
CAN_frame BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x4D}}; //Main Battery Voltage (Pre Contactor)
|
||||
CAN_frame BMWiX_6F4_REQUEST_BATTERYCURRENT = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x61}}; //Current amps 32bit signed MSB. dA . negative is discharge
|
||||
CAN_frame BMWiX_6F4_REQUEST_CELL_VOLTAGE = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x54}}; //MultiFrameIndividual Cell Voltages
|
||||
CAN_frame BMWiX_6F4_REQUEST_T30VOLTAGE = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0xA7}}; //Terminal 30 Voltage (12V SME supply)
|
||||
CAN_frame BMWiX_6F4_REQUEST_EOL_ISO = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xA8, 0x60}}; //Request EOL Reading including ISO
|
||||
CAN_frame BMWiX_6F4_REQUEST_SOH = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x45}}; //SOH Max Min Mean Request
|
||||
CAN_frame BMWiX_6F4_REQUEST_DATASUMMARY = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {
|
||||
0x07, 0x03, 0x22, 0xE5,
|
||||
0x45}}; //MultiFrame Summary Request, includes SOC/SOH/MinMax/MaxCapac/RemainCapac/max v and t at last charge. slow refreshrate
|
||||
CAN_frame BMWiX_6F4_REQUEST_PYRO = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xAC, 0x93}}; //Pyro Status
|
||||
CAN_frame BMWiX_6F4_REQUEST_UPTIME = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE4, 0xC0}}; // Uptime and Vehicle Time Status
|
||||
CAN_frame BMWiX_6F4_REQUEST_HVIL = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x69}}; // Request HVIL State
|
||||
CAN_frame BMWiX_6F4_REQUEST_BALANCINGSTATUS = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE4, 0xCA}}; // Request Balancing Data
|
||||
CAN_frame BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x62}}; // Request allowable charge discharge amps
|
||||
CAN_frame BMWiX_6F4_REQUEST_VOLTAGE_QUALIFIER_CHECK = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x4B}}; // Request HV Voltage Qualifier
|
||||
CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_CLOSE = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 6,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Close - Unconfirmed
|
||||
CAN_frame BMWiX_6F4_REQUEST_CONTACTORS_OPEN = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 6,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x51, 0x01}}; // Request Contactors Open - Unconfirmed
|
||||
CAN_frame BMWiX_6F4_REQUEST_BALANCING_START = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 6,
|
||||
.ID = 0x6F4,
|
||||
.data = {0xF4, 0x04, 0x71, 0x01, 0xAE, 0x77}}; // Request Balancing command?
|
||||
CAN_frame BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS = {
|
||||
.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x4C}}; // Request pack voltage limits
|
||||
|
||||
CAN_frame BMWiX_6F4_CONTINUE_DATA = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 4,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x30, 0x00, 0x02}};
|
||||
|
||||
//Action Requests:
|
||||
CAN_frame BMW_10B = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 3,
|
||||
.ID = 0x10B,
|
||||
.data = {0xCD, 0x00, 0xFC}}; // Contactor closing command?
|
||||
|
||||
CAN_frame BMWiX_6F4_CELL_SOC = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0x9A}};
|
||||
CAN_frame BMWiX_6F4_CELL_TEMP = {.FD = true,
|
||||
.ext_ID = false,
|
||||
.DLC = 5,
|
||||
.ID = 0x6F4,
|
||||
.data = {0x07, 0x03, 0x22, 0xE5, 0xCA}};
|
||||
//Request Data CAN End
|
||||
|
||||
static bool battery_awake = false;
|
||||
|
||||
//Setup UDS values to poll for
|
||||
CAN_frame* UDS_REQUESTS100MS[] = {&BMWiX_6F4_REQUEST_CELL_TEMP,
|
||||
&BMWiX_6F4_REQUEST_SOC,
|
||||
&BMWiX_6F4_REQUEST_CAPACITY,
|
||||
&BMWiX_6F4_REQUEST_MINMAXCELLV,
|
||||
&BMWiX_6F4_REQUEST_MAINVOLTAGE_POSTCONTACTOR,
|
||||
&BMWiX_6F4_REQUEST_MAINVOLTAGE_PRECONTACTOR,
|
||||
&BMWiX_6F4_REQUEST_BATTERYCURRENT,
|
||||
&BMWiX_6F4_REQUEST_CELL_VOLTAGE,
|
||||
&BMWiX_6F4_REQUEST_T30VOLTAGE,
|
||||
&BMWiX_6F4_REQUEST_SOH,
|
||||
&BMWiX_6F4_REQUEST_UPTIME,
|
||||
&BMWiX_6F4_REQUEST_PYRO,
|
||||
&BMWiX_6F4_REQUEST_EOL_ISO,
|
||||
&BMWiX_6F4_REQUEST_HVIL,
|
||||
&BMWiX_6F4_REQUEST_MAX_CHARGE_DISCHARGE_AMPS,
|
||||
&BMWiX_6F4_REQUEST_BALANCINGSTATUS,
|
||||
&BMWiX_6F4_REQUEST_PACK_VOLTAGE_LIMITS};
|
||||
int numUDSreqs = sizeof(UDS_REQUESTS100MS) / sizeof(UDS_REQUESTS100MS[0]); // Number of elements in the array
|
||||
|
||||
//iX Intermediate vars
|
||||
static bool battery_info_available = false;
|
||||
static uint32_t battery_serial_number = 0;
|
||||
static int32_t battery_current = 0;
|
||||
static int16_t battery_voltage = 370;
|
||||
static int16_t terminal30_12v_voltage = 0;
|
||||
static int16_t battery_voltage_after_contactor = 0;
|
||||
static int16_t min_soc_state = 50;
|
||||
static int16_t avg_soc_state = 50;
|
||||
static int16_t max_soc_state = 50;
|
||||
static int16_t min_soh_state = 99; // Uses E5 45, also available in 78 73
|
||||
static int16_t avg_soh_state = 99; // Uses E5 45, also available in 78 73
|
||||
static int16_t max_soh_state = 99; // Uses E5 45, also available in 78 73
|
||||
static uint16_t max_design_voltage = 0;
|
||||
static uint16_t min_design_voltage = 0;
|
||||
static int32_t remaining_capacity = 0;
|
||||
static int32_t max_capacity = 0;
|
||||
static int16_t min_battery_temperature = 0;
|
||||
static int16_t avg_battery_temperature = 0;
|
||||
static int16_t max_battery_temperature = 0;
|
||||
static int16_t main_contactor_temperature = 0;
|
||||
static int16_t min_cell_voltage = 0;
|
||||
static int16_t max_cell_voltage = 0;
|
||||
static unsigned long min_cell_voltage_lastchanged = 0;
|
||||
static unsigned long max_cell_voltage_lastchanged = 0;
|
||||
static unsigned min_cell_voltage_lastreceived = 0;
|
||||
static unsigned max_cell_voltage_lastreceived = 0;
|
||||
static uint32_t sme_uptime = 0; //Uses E4 C0
|
||||
static int16_t allowable_charge_amps = 0; //E5 62
|
||||
static int16_t allowable_discharge_amps = 0; //E5 62
|
||||
static int32_t iso_safety_positive = 0; //Uses A8 60
|
||||
static int32_t iso_safety_negative = 0; //Uses A8 60
|
||||
static int32_t iso_safety_parallel = 0; //Uses A8 60
|
||||
static int16_t count_full_charges = 0; //TODO 42
|
||||
static int16_t count_charges = 0; //TODO 42
|
||||
static int16_t hvil_status = 0;
|
||||
static int16_t voltage_qualifier_status = 0; //0 = Valid, 1 = Invalid
|
||||
static int16_t balancing_status = 0; //4 = not active
|
||||
static uint8_t contactors_closed = 0; //TODO E5 BF or E5 51
|
||||
static uint8_t contactor_status_precharge = 0; //TODO E5 BF
|
||||
static uint8_t contactor_status_negative = 0; //TODO E5 BF
|
||||
static uint8_t contactor_status_positive = 0; //TODO E5 BF
|
||||
static uint8_t pyro_status_pss1 = 0; //Using AC 93
|
||||
static uint8_t pyro_status_pss4 = 0; //Using AC 93
|
||||
static uint8_t pyro_status_pss6 = 0; //Using AC 93
|
||||
static uint8_t uds_req_id_counter = 0;
|
||||
static uint8_t detected_number_of_cells = 108;
|
||||
const unsigned long STALE_PERIOD =
|
||||
STALE_PERIOD_CONFIG; // Time in milliseconds to check for staleness (e.g., 5000 ms = 5 seconds)
|
||||
|
||||
static byte iX_0C0_counter = 0xF0; // Initialize to 0xF0
|
||||
|
||||
//End iX Intermediate vars
|
||||
|
||||
static uint8_t current_cell_polled = 0;
|
||||
|
||||
// Function to check if a value has gone stale over a specified time period
|
||||
bool isStale(int16_t currentValue, uint16_t& lastValue, unsigned long& lastChangeTime) {
|
||||
unsigned long currentTime = millis();
|
||||
|
||||
// Check if the value has changed
|
||||
if (currentValue != lastValue) {
|
||||
// Update the last change time and value
|
||||
lastChangeTime = currentTime;
|
||||
lastValue = currentValue;
|
||||
return false; // Value is fresh because it has changed
|
||||
}
|
||||
|
||||
// Check if the value has stayed the same for the specified staleness period
|
||||
return (currentTime - lastChangeTime >= STALE_PERIOD);
|
||||
}
|
||||
|
||||
static uint8_t increment_uds_req_id_counter(uint8_t index) {
|
||||
index++;
|
||||
if (index >= numUDSreqs) {
|
||||
index = 0;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
static uint8_t increment_alive_counter(uint8_t counter) {
|
||||
counter++;
|
||||
if (counter > ALIVE_MAX_VALUE) {
|
||||
counter = 0;
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
static byte increment_0C0_counter(byte counter) {
|
||||
counter++;
|
||||
// Reset to 0xF0 if it exceeds 0xFE
|
||||
if (counter > 0xFE) {
|
||||
counter = 0xF0;
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
|
||||
|
||||
datalayer.battery.status.real_soc = avg_soc_state;
|
||||
|
||||
datalayer.battery.status.voltage_dV = battery_voltage;
|
||||
|
||||
datalayer.battery.status.current_dA = battery_current;
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh = max_capacity;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = remaining_capacity;
|
||||
|
||||
datalayer.battery.status.soh_pptt = min_soh_state;
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = MAX_DISCHARGE_POWER_ALLOWED_W;
|
||||
|
||||
//datalayer.battery.status.max_charge_power_W = 3200; //10000; //Aux HV Port has 100A Fuse Moved to Ramping
|
||||
|
||||
// Charge power is set in .h file
|
||||
if (datalayer.battery.status.real_soc > 9900) {
|
||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_WHEN_TOPBALANCING_W;
|
||||
} else if (datalayer.battery.status.real_soc > RAMPDOWN_SOC) {
|
||||
// When real SOC is between RAMPDOWN_SOC-99%, ramp the value between Max<->0
|
||||
datalayer.battery.status.max_charge_power_W =
|
||||
MAX_CHARGE_POWER_ALLOWED_W *
|
||||
(1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
||||
} else { // No limits, max charging power allowed
|
||||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W;
|
||||
}
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = min_battery_temperature;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = max_battery_temperature;
|
||||
|
||||
//Check stale values. As values dont change much during idle only consider stale if both parts of this message freeze.
|
||||
bool isMinCellVoltageStale =
|
||||
isStale(min_cell_voltage, datalayer.battery.status.cell_min_voltage_mV, min_cell_voltage_lastchanged);
|
||||
bool isMaxCellVoltageStale =
|
||||
isStale(max_cell_voltage, datalayer.battery.status.cell_max_voltage_mV, max_cell_voltage_lastchanged);
|
||||
|
||||
if (isMinCellVoltageStale && isMaxCellVoltageStale) {
|
||||
datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop
|
||||
datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
} else {
|
||||
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive
|
||||
datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive
|
||||
}
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV = max_design_voltage;
|
||||
|
||||
datalayer.battery.info.min_design_voltage_dV = min_design_voltage;
|
||||
|
||||
datalayer.battery.info.number_of_cells = detected_number_of_cells;
|
||||
|
||||
datalayer_extended.bmwix.min_cell_voltage_data_age = (millis() - min_cell_voltage_lastchanged);
|
||||
|
||||
datalayer_extended.bmwix.max_cell_voltage_data_age = (millis() - max_cell_voltage_lastchanged);
|
||||
|
||||
datalayer_extended.bmwix.T30_Voltage = terminal30_12v_voltage;
|
||||
|
||||
datalayer_extended.bmwix.hvil_status = hvil_status;
|
||||
|
||||
datalayer_extended.bmwix.bms_uptime = sme_uptime;
|
||||
|
||||
datalayer_extended.bmwix.pyro_status_pss1 = pyro_status_pss1;
|
||||
|
||||
datalayer_extended.bmwix.pyro_status_pss4 = pyro_status_pss4;
|
||||
|
||||
datalayer_extended.bmwix.pyro_status_pss6 = pyro_status_pss6;
|
||||
|
||||
datalayer_extended.bmwix.iso_safety_positive = iso_safety_positive;
|
||||
|
||||
datalayer_extended.bmwix.iso_safety_negative = iso_safety_negative;
|
||||
|
||||
datalayer_extended.bmwix.iso_safety_parallel = iso_safety_parallel;
|
||||
|
||||
datalayer_extended.bmwix.allowable_charge_amps = allowable_charge_amps;
|
||||
|
||||
datalayer_extended.bmwix.allowable_discharge_amps = allowable_discharge_amps;
|
||||
|
||||
datalayer_extended.bmwix.balancing_status = balancing_status;
|
||||
|
||||
datalayer_extended.bmwix.battery_voltage_after_contactor = battery_voltage_after_contactor;
|
||||
|
||||
if (battery_info_available) {
|
||||
// If we have data from battery - override the defaults to suit
|
||||
datalayer.battery.info.max_design_voltage_dV = max_design_voltage;
|
||||
datalayer.battery.info.min_design_voltage_dV = min_design_voltage;
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
||||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
||||
}
|
||||
}
|
||||
void receive_can_battery(CAN_frame rx_frame) {
|
||||
battery_awake = true;
|
||||
switch (rx_frame.ID) {
|
||||
case 0x112:
|
||||
break;
|
||||
case 0x607: //SME responds to UDS requests on 0x607
|
||||
|
||||
if (rx_frame.DLC > 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x10 &&
|
||||
rx_frame.data.u8[2] == 0xE3 && rx_frame.data.u8[3] == 0x62 && rx_frame.data.u8[4] == 0xE5) {
|
||||
//First of multi frame data - Parse the first frame
|
||||
if (rx_frame.DLC = 64 && rx_frame.data.u8[5] == 0x54) { //Individual Cell Voltages - First Frame
|
||||
int start_index = 6; //Data starts here
|
||||
int voltage_index = 0; //Start cell ID
|
||||
int num_voltages = 29; // number of voltage readings to get
|
||||
for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
|
||||
uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
|
||||
if (voltage < 10000) { //Check reading is plausible - otherwise ignore
|
||||
datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
|
||||
}
|
||||
voltage_index++;
|
||||
}
|
||||
}
|
||||
|
||||
//Frame has continued data - so request it
|
||||
transmit_can(&BMWiX_6F4_CONTINUE_DATA, can_config.battery);
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 &&
|
||||
rx_frame.data.u8[1] == 0x21) { //Individual Cell Voltages - 1st Continue frame
|
||||
int start_index = 2; //Data starts here
|
||||
int voltage_index = 29; //Start cell ID
|
||||
int num_voltages = 31; // number of voltage readings to get
|
||||
for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
|
||||
uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
|
||||
if (voltage < 10000) { //Check reading is plausible - otherwise ignore
|
||||
datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
|
||||
}
|
||||
voltage_index++;
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 &&
|
||||
rx_frame.data.u8[1] == 0x22) { //Individual Cell Voltages - 2nd Continue frame
|
||||
int start_index = 2; //Data starts here
|
||||
int voltage_index = 60; //Start cell ID
|
||||
int num_voltages = 31; // number of voltage readings to get
|
||||
for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
|
||||
uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
|
||||
if (voltage < 10000) { //Check reading is plausible - otherwise ignore
|
||||
datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
|
||||
}
|
||||
voltage_index++;
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 64 && rx_frame.data.u8[0] == 0xF4 &&
|
||||
rx_frame.data.u8[1] == 0x23) { //Individual Cell Voltages - 3rd Continue frame
|
||||
int start_index = 2; //Data starts here
|
||||
int voltage_index = 91; //Start cell ID
|
||||
int num_voltages;
|
||||
if (rx_frame.data.u8[12] == 0xFF && rx_frame.data.u8[13] == 0xFF) { //97th cell is blank - assume 96S Battery
|
||||
num_voltages = 5; // number of voltage readings to get - 6 more to get on 96S
|
||||
detected_number_of_cells = 96;
|
||||
} else { //We have data in 97th cell, assume 108S Battery
|
||||
num_voltages = 17; // number of voltage readings to get - 17 more to get on 108S
|
||||
detected_number_of_cells = 108;
|
||||
}
|
||||
|
||||
for (int i = start_index; i < (start_index + num_voltages * 2); i += 2) {
|
||||
uint16_t voltage = (rx_frame.data.u8[i] << 8) | rx_frame.data.u8[i + 1];
|
||||
if (voltage < 10000) { //Check reading is plausible - otherwise ignore
|
||||
datalayer.battery.status.cell_voltages_mV[voltage_index] = voltage;
|
||||
}
|
||||
voltage_index++;
|
||||
}
|
||||
}
|
||||
if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4D) { //Main Battery Voltage (Pre Contactor)
|
||||
battery_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10;
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0x4A) { //Main Battery Voltage (After Contactor)
|
||||
battery_voltage_after_contactor = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]) / 10;
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 &&
|
||||
rx_frame.data.u8[5] == 0x61) { //Current amps 32bit signed MSB. dA . negative is discharge
|
||||
battery_current = ((int32_t)((rx_frame.data.u8[6] << 24) | (rx_frame.data.u8[7] << 16) |
|
||||
(rx_frame.data.u8[8] << 8) | rx_frame.data.u8[9])) *
|
||||
0.1;
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 64 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xCA) { //Balancing Data
|
||||
balancing_status = (rx_frame.data.u8[6]); //4 = No symmetry mode active, invalid qualifier
|
||||
}
|
||||
if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0xCE) { //Min/Avg/Max SOC%
|
||||
min_soc_state = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]);
|
||||
avg_soc_state = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
|
||||
max_soc_state = (rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]);
|
||||
}
|
||||
|
||||
if (rx_frame.DLC =
|
||||
12 && rx_frame.data.u8[4] == 0xE5 &&
|
||||
rx_frame.data.u8[5] == 0xC7) { //Current and max capacity kWh. Stored in kWh as 0.01 scale with -50 bias
|
||||
remaining_capacity = ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) * 10) - 50000;
|
||||
max_capacity = ((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) * 10) - 50000;
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 20 && rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x45) { //SOH Max Min Mean Request
|
||||
min_soh_state = ((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]));
|
||||
avg_soh_state = ((rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]));
|
||||
max_soh_state = ((rx_frame.data.u8[12] << 8 | rx_frame.data.u8[13]));
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 10 && rx_frame.data.u8[4] == 0xE5 &&
|
||||
rx_frame.data.u8[5] == 0x62) { //Max allowed charge and discharge current - Signed 16bit
|
||||
allowable_charge_amps = (int16_t)((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7])) / 10;
|
||||
allowable_discharge_amps = (int16_t)((rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9])) / 10;
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 9 && rx_frame.data.u8[4] == 0xE5 &&
|
||||
rx_frame.data.u8[5] == 0x4B) { //Max allowed charge and discharge current - Signed 16bit
|
||||
voltage_qualifier_status = (rx_frame.data.u8[8]); // Request HV Voltage Qualifier
|
||||
}
|
||||
|
||||
if (rx_frame.DLC =
|
||||
48 && rx_frame.data.u8[4] == 0xA8 && rx_frame.data.u8[5] == 0x60) { // Safety Isolation Measurements
|
||||
iso_safety_positive = (rx_frame.data.u8[34] << 24) | (rx_frame.data.u8[35] << 16) |
|
||||
(rx_frame.data.u8[36] << 8) | rx_frame.data.u8[37]; //Assuming 32bit
|
||||
iso_safety_negative = (rx_frame.data.u8[38] << 24) | (rx_frame.data.u8[39] << 16) |
|
||||
(rx_frame.data.u8[40] << 8) | rx_frame.data.u8[41]; //Assuming 32bit
|
||||
iso_safety_parallel = (rx_frame.data.u8[42] << 24) | (rx_frame.data.u8[43] << 16) |
|
||||
(rx_frame.data.u8[44] << 8) | rx_frame.data.u8[45]; //Assuming 32bit
|
||||
}
|
||||
|
||||
if (rx_frame.DLC =
|
||||
48 && rx_frame.data.u8[4] == 0xE4 && rx_frame.data.u8[5] == 0xC0) { // Uptime and Vehicle Time Status
|
||||
sme_uptime = (rx_frame.data.u8[10] << 24) | (rx_frame.data.u8[11] << 16) | (rx_frame.data.u8[12] << 8) |
|
||||
rx_frame.data.u8[13]; //Assuming 32bit
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 8 && rx_frame.data.u8[3] == 0xAC && rx_frame.data.u8[4] == 0x93) { // Pyro Status
|
||||
pyro_status_pss1 = (rx_frame.data.u8[5]);
|
||||
pyro_status_pss4 = (rx_frame.data.u8[6]);
|
||||
pyro_status_pss6 = (rx_frame.data.u8[7]);
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 12 && rx_frame.data.u8[4] == 0xE5 &&
|
||||
rx_frame.data.u8[5] == 0x53) { //Min and max cell voltage 10V = Qualifier Invalid
|
||||
|
||||
datalayer.battery.status.CAN_battery_still_alive =
|
||||
CAN_STILL_ALIVE; //This is the most important safety values, if we receive this we reset CAN alive counter.
|
||||
|
||||
if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) == 10000 ||
|
||||
(rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) == 10000) { //Qualifier Invalid Mode - Request Reboot
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Cell MinMax Qualifier Invalid - Requesting BMS Reset");
|
||||
#endif
|
||||
//set_event(EVENT_BATTERY_VALUE_UNAVAILABLE, (millis())); //Eventually need new Info level event type
|
||||
transmit_can(&BMWiX_6F4_REQUEST_HARD_RESET, can_config.battery);
|
||||
} else { //Only ingest values if they are not the 10V Error state
|
||||
min_cell_voltage = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
|
||||
max_cell_voltage = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 16 && rx_frame.data.u8[4] == 0xDD && rx_frame.data.u8[5] == 0xC0) { //Battery Temperature
|
||||
min_battery_temperature = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) / 10;
|
||||
avg_battery_temperature = (rx_frame.data.u8[10] << 8 | rx_frame.data.u8[11]) / 10;
|
||||
max_battery_temperature = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) / 10;
|
||||
}
|
||||
if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA3) { //Main Contactor Temperature CHECK FINGERPRINT 2 LEVEL
|
||||
main_contactor_temperature = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]);
|
||||
}
|
||||
if (rx_frame.DLC = 7 && rx_frame.data.u8[4] == 0xA7) { //Terminal 30 Voltage (12V SME supply)
|
||||
terminal30_12v_voltage = (rx_frame.data.u8[5] << 8 | rx_frame.data.u8[6]);
|
||||
}
|
||||
if (rx_frame.DLC = 6 && rx_frame.data.u8[0] == 0xF4 && rx_frame.data.u8[1] == 0x04 &&
|
||||
rx_frame.data.u8[2] == 0x62 && rx_frame.data.u8[3] == 0xE5 &&
|
||||
rx_frame.data.u8[4] == 0x69) { //HVIL Status
|
||||
hvil_status = (rx_frame.data.u8[5]);
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 12 && rx_frame.data.u8[2] == 0x07 && rx_frame.data.u8[3] == 0x62 &&
|
||||
rx_frame.data.u8[4] == 0xE5 && rx_frame.data.u8[5] == 0x4C) { //Pack Voltage Limits
|
||||
if ((rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]) < 4700 &&
|
||||
(rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]) > 2600) { //Make sure values are plausible
|
||||
battery_info_available = true;
|
||||
max_design_voltage = (rx_frame.data.u8[6] << 8 | rx_frame.data.u8[7]);
|
||||
min_design_voltage = (rx_frame.data.u8[8] << 8 | rx_frame.data.u8[9]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_frame.DLC = 16 && rx_frame.data.u8[3] == 0xF1 && rx_frame.data.u8[4] == 0x8C) { //Battery Serial Number
|
||||
//Convert hex bytes to ASCII characters and combine them into a string
|
||||
char numberString[11]; // 10 characters + null terminator
|
||||
for (int i = 0; i < 10; i++) {
|
||||
numberString[i] = char(rx_frame.data.u8[i + 6]);
|
||||
}
|
||||
numberString[10] = '\0'; // Null-terminate the string
|
||||
// Step 3: Convert the string to an unsigned long integer
|
||||
battery_serial_number = strtoul(numberString, NULL, 10);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
//if (battery_awake) { //We can always send CAN as the iX BMS will wake up on vehicle comms
|
||||
// Send 100ms CAN Message
|
||||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
//Loop through and send a different UDS request each cycle
|
||||
uds_req_id_counter = increment_uds_req_id_counter(uds_req_id_counter);
|
||||
transmit_can(UDS_REQUESTS100MS[uds_req_id_counter], can_config.battery);
|
||||
|
||||
//Send SME Keep alive values 100ms
|
||||
transmit_can(&BMWiX_510, can_config.battery);
|
||||
}
|
||||
// Send 200ms CAN Message
|
||||
if (currentMillis - previousMillis200 >= INTERVAL_200_MS) {
|
||||
previousMillis200 = currentMillis;
|
||||
|
||||
//Send SME Keep alive values 200ms
|
||||
BMWiX_0C0.data.u8[0] = increment_0C0_counter(BMWiX_0C0.data.u8[0]); //Keep Alive 1
|
||||
transmit_can(&BMWiX_0C0, can_config.battery);
|
||||
}
|
||||
// Send 1000ms CAN Message
|
||||
if (currentMillis - previousMillis1000 >= INTERVAL_1_S) {
|
||||
previousMillis1000 = currentMillis;
|
||||
|
||||
//Send SME Keep alive values 1000ms
|
||||
//Don't believe this is needed: transmit_can(&BMWiX_06D, can_config.battery);
|
||||
//Don't believe this is needed: transmit_can(&BMWiX_2F1, can_config.battery);
|
||||
//Don't believe this is needed: transmit_can(&BMWiX_439, can_config.battery);
|
||||
}
|
||||
// Send 5000ms CAN Message
|
||||
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
|
||||
previousMillis5000 = currentMillis;
|
||||
}
|
||||
// Send 10000ms CAN Message
|
||||
if (currentMillis - previousMillis10000 >= INTERVAL_10_S) {
|
||||
previousMillis10000 = currentMillis;
|
||||
}
|
||||
}
|
||||
//We can always send CAN as the iX BMS will wake up on vehicle comms
|
||||
// else {
|
||||
// previousMillis20 = currentMillis;
|
||||
// previousMillis100 = currentMillis;
|
||||
// previousMillis200 = currentMillis;
|
||||
// previousMillis500 = currentMillis;
|
||||
// previousMillis640 = currentMillis;
|
||||
// previousMillis1000 = currentMillis;
|
||||
// previousMillis5000 = currentMillis;
|
||||
// previousMillis10000 = currentMillis;
|
||||
// }
|
||||
//} //We can always send CAN as the iX BMS will wake up on vehicle comms
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "BMW iX and i4-7 platform", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
//Before we have started up and detected which battery is in use, use 108S values
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
||||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
||||
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
}
|
||||
|
||||
#endif
|
23
Software/src/battery/BMW-IX-BATTERY.h
Normal file
23
Software/src/battery/BMW-IX-BATTERY.h
Normal 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
|
|
@ -15,6 +15,7 @@
|
|||
static unsigned long previousMillis50 = 0; // will store last time a 50ms CAN Message was send
|
||||
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
|
||||
static unsigned long previousMillis500 = 0; // will store last time a 500ms CAN Message was send
|
||||
static bool SOC_method = false;
|
||||
static uint8_t counter_50ms = 0;
|
||||
static uint8_t counter_100ms = 0;
|
||||
static uint8_t frame6_counter = 0xB;
|
||||
|
@ -61,6 +62,8 @@ static uint16_t BMS2_highest_cell_voltage_mV = 3300;
|
|||
#define POLL_FOR_BATTERY_CELL_MV_MAX 0x2D
|
||||
#define POLL_FOR_BATTERY_CELL_MV_MIN 0x2B
|
||||
#define UNKNOWN_POLL_1 0xFC
|
||||
#define ESTIMATED 0
|
||||
#define MEASURED 1
|
||||
static uint16_t poll_state = POLL_FOR_BATTERY_SOC;
|
||||
|
||||
CAN_frame ATTO_3_12D = {.FD = false,
|
||||
|
@ -108,22 +111,25 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.voltage_dV = BMS_voltage * 10;
|
||||
}
|
||||
|
||||
//datalayer.battery.status.real_soc = BMS_SOC * 100; //TODO: This is not yet found!
|
||||
// We instead estimate the SOC% based on the battery voltage
|
||||
// This is a very bad solution, and as soon as an usable SOC% value has been found on CAN, we should switch to that!
|
||||
#ifdef USE_ESTIMATED_SOC
|
||||
// When the battery is crashed hard, it locks itself and SOC becomes unavailable.
|
||||
// We instead estimate the SOC% based on the battery voltage.
|
||||
// This is a bad solution, you wont be able to use 100% of the battery
|
||||
datalayer.battery.status.real_soc = estimateSOC(datalayer.battery.status.voltage_dV);
|
||||
SOC_method = ESTIMATED;
|
||||
#else // Pack is not crashed, we can use periodically transmitted SOC
|
||||
datalayer.battery.status.real_soc = battery_highprecision_SOC * 100;
|
||||
SOC_method = MEASURED;
|
||||
#endif
|
||||
|
||||
datalayer.battery.status.current_dA = -BMS_current;
|
||||
|
||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = 10000; //TODO: Map from CAN later on
|
||||
datalayer.battery.status.max_discharge_power_W = MAXPOWER_DISCHARGE_W; //TODO: Map from CAN later on
|
||||
|
||||
datalayer.battery.status.max_charge_power_W = 10000; //TODO: Map from CAN later on
|
||||
|
||||
datalayer.battery.status.active_power_W =
|
||||
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
|
||||
datalayer.battery.status.max_charge_power_W = MAXPOWER_CHARGE_W; //TODO: Map from CAN later on
|
||||
|
||||
datalayer.battery.status.cell_max_voltage_mV = BMS_highest_cell_voltage_mV;
|
||||
|
||||
|
@ -147,6 +153,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10;
|
||||
|
||||
// Update webserver datalayer
|
||||
datalayer_extended.bydAtto3.SOC_method = SOC_method;
|
||||
datalayer_extended.bydAtto3.SOC_estimated = datalayer.battery.status.real_soc;
|
||||
//Once we implement switching logic, remember to change from where the estimated is taken
|
||||
datalayer_extended.bydAtto3.SOC_highprec = battery_highprecision_SOC;
|
||||
|
@ -231,6 +238,7 @@ void receive_can_battery(CAN_frame rx_frame) {
|
|||
case 0x444: //9E,01,88,13,64,64,98,65
|
||||
//9A,01,B6,13,64,64,98,3B //407.5V 18deg
|
||||
//9B,01,B8,13,64,64,98,38 //408.5V 14deg
|
||||
//lowprecision_SOC = ???
|
||||
battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0];
|
||||
//battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7
|
||||
break;
|
||||
|
@ -402,9 +410,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 +452,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;
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
|
||||
#include "../include.h"
|
||||
|
||||
#define USE_ESTIMATED_SOC // If enabled, SOC is estimated from pack voltage. Useful for locked packs. \
|
||||
// Uncomment this only if you know your BMS is unlocked and able to send SOC%
|
||||
#define MAXPOWER_CHARGE_W 10000
|
||||
#define MAXPOWER_DISCHARGE_W 10000
|
||||
|
||||
/* Do not modify the rows below */
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_PACK_VOLTAGE_DV 4410 //5000 = 500.0V
|
||||
#define MIN_PACK_VOLTAGE_DV 3800
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -120,7 +120,7 @@ void update_values_battery() {
|
|||
datalayer.battery.status.voltage_dV = get_measured_voltage() * 10;
|
||||
|
||||
datalayer.battery.info.total_capacity_Wh =
|
||||
((x101_chg_est.RatedBatteryCapacity / 0.11) *
|
||||
((x101_chg_est.RatedBatteryCapacity / 0.1) *
|
||||
1000); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
|
||||
|
||||
/* TODO max charging rate =
|
||||
|
@ -151,8 +151,8 @@ void update_values_battery() {
|
|||
|
||||
inline void process_vehicle_charging_minimums(CAN_frame rx_frame) {
|
||||
x100_chg_lim.MinimumChargeCurrent = rx_frame.data.u8[0];
|
||||
x100_chg_lim.MinimumBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3]);
|
||||
x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
x100_chg_lim.MinimumBatteryVoltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
|
||||
x100_chg_lim.MaximumBatteryVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
|
||||
x100_chg_lim.ConstantOfChargingRateIndication = rx_frame.data.u8[6];
|
||||
}
|
||||
|
||||
|
@ -160,15 +160,14 @@ inline void process_vehicle_charging_maximums(CAN_frame rx_frame) {
|
|||
x101_chg_est.MaxChargingTime10sBit = rx_frame.data.u8[1];
|
||||
x101_chg_est.MaxChargingTime1minBit = rx_frame.data.u8[2];
|
||||
x101_chg_est.EstimatedChargingTime = rx_frame.data.u8[3];
|
||||
x101_chg_est.RatedBatteryCapacity = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[6]);
|
||||
x101_chg_est.RatedBatteryCapacity = ((rx_frame.data.u8[6] << 8) | rx_frame.data.u8[5]);
|
||||
}
|
||||
|
||||
inline void process_vehicle_charging_session(CAN_frame rx_frame) {
|
||||
|
||||
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
||||
uint16_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
|
||||
uint8_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
|
||||
uint16_t newTargetBatteryVoltage = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
|
||||
uint16_t priorTargetBatteryVoltage = x102_chg_session.TargetBatteryVoltage;
|
||||
uint8_t newChargingCurrentRequest = rx_frame.data.u8[3];
|
||||
uint8_t priorChargingCurrentRequest = x102_chg_session.ChargingCurrentRequest;
|
||||
|
||||
vehicle_can_initialized = true;
|
||||
|
||||
|
@ -187,6 +186,7 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
|
|||
x102_chg_session.s.status.StatusChargingError = bitRead(rx_frame.data.u8[5], 2);
|
||||
x102_chg_session.s.status.StatusVehicle = bitRead(rx_frame.data.u8[5], 3);
|
||||
x102_chg_session.s.status.StatusNormalStopRequest = bitRead(rx_frame.data.u8[5], 4);
|
||||
x102_chg_session.s.status.StatusVehicleDischargeCompatible = bitRead(rx_frame.data.u8[5], 7);
|
||||
|
||||
x102_chg_session.StateOfCharge = rx_frame.data.u8[6];
|
||||
|
||||
|
@ -202,7 +202,7 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
|
|||
uint8_t chargingrate = 0;
|
||||
if (x100_chg_lim.ConstantOfChargingRateIndication > 0) {
|
||||
chargingrate = x102_chg_session.StateOfCharge / x100_chg_lim.ConstantOfChargingRateIndication * 100;
|
||||
Serial.print("Charge Rate (kW):");
|
||||
Serial.print("Charge Rate (kW): ");
|
||||
Serial.println(chargingrate);
|
||||
}
|
||||
#endif
|
||||
|
@ -308,7 +308,7 @@ inline void process_vehicle_charging_session(CAN_frame rx_frame) {
|
|||
inline void process_vehicle_charging_limits(CAN_frame rx_frame) {
|
||||
|
||||
x200_discharge_limits.MaximumDischargeCurrent = rx_frame.data.u8[0];
|
||||
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5]);
|
||||
x200_discharge_limits.MinimumDischargeVoltage = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
|
||||
x200_discharge_limits.MinimumBatteryDischargeLevel = rx_frame.data.u8[6];
|
||||
x200_discharge_limits.MaxRemainingCapacityForCharging = rx_frame.data.u8[7];
|
||||
|
||||
|
@ -338,15 +338,15 @@ inline void process_vehicle_discharge_estimate(CAN_frame rx_frame) {
|
|||
unsigned long currentMillis = millis();
|
||||
|
||||
x201_discharge_estimate.V2HchargeDischargeSequenceNum = rx_frame.data.u8[0];
|
||||
x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]);
|
||||
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[4]);
|
||||
x201_discharge_estimate.ApproxDischargeCompletionTime = ((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]);
|
||||
x201_discharge_estimate.AvailableVehicleEnergy = ((rx_frame.data.u8[4] << 8) | rx_frame.data.u8[3]);
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
if (currentMillis - previousMillis5000 >= INTERVAL_5_S) {
|
||||
previousMillis5000 = currentMillis;
|
||||
Serial.println("x201 availabile vehicle energy, completion time");
|
||||
Serial.print("x201 availabile vehicle energy, completion time: ");
|
||||
Serial.println(x201_discharge_estimate.AvailableVehicleEnergy);
|
||||
Serial.println("x201 approx vehicle completion time");
|
||||
Serial.print("x201 approx vehicle completion time: ");
|
||||
Serial.println(x201_discharge_estimate.ApproxDischargeCompletionTime);
|
||||
}
|
||||
#endif
|
||||
|
@ -364,7 +364,7 @@ inline void process_vehicle_dynamic_control(CAN_frame rx_frame) {
|
|||
inline void process_vehicle_vendor_ID(CAN_frame rx_frame) {
|
||||
x700_vendor_id.AutomakerCode = rx_frame.data.u8[0];
|
||||
x700_vendor_id.OptionalContent =
|
||||
((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[2]); //Actually more bytes, but not needed for our purpose
|
||||
((rx_frame.data.u8[2] << 8) | rx_frame.data.u8[1]); //Actually more bytes, but not needed for our purpose
|
||||
}
|
||||
|
||||
void receive_can_battery(CAN_frame rx_frame) {
|
||||
|
@ -557,7 +557,7 @@ void update_evse_status(CAN_frame& f) {
|
|||
*
|
||||
*/
|
||||
if ((x102_chg_session.TargetBatteryVoltage > x108_evse_cap.available_output_voltage) ||
|
||||
(x100_chg_lim.MaximumBatteryVoltage > x108_evse_cap.threshold_voltage)) {
|
||||
(x100_chg_lim.MaximumBatteryVoltage < x108_evse_cap.threshold_voltage)) {
|
||||
//Toggle battery incompatibility flag 109.5.3
|
||||
x109_evse_state.s.status.EVSE_error = 1;
|
||||
x109_evse_state.s.status.battery_incompatible = 1;
|
||||
|
@ -602,7 +602,8 @@ void update_evse_discharge_estimate(CAN_frame& f) {
|
|||
*/
|
||||
|
||||
CHADEMO_209.data.u8[0] = x209_evse_dischg_est.sequence_control_number;
|
||||
CHADEMO_209.data.u8[1] = x209_evse_dischg_est.remaining_discharge_time;
|
||||
CHADEMO_209.data.u8[1] = lowByte(x209_evse_dischg_est.remaining_discharge_time);
|
||||
CHADEMO_209.data.u8[2] = highByte(x209_evse_dischg_est.remaining_discharge_time);
|
||||
}
|
||||
|
||||
/* x208 EVSE, peer to 0x200 Vehicle */
|
||||
|
@ -751,7 +752,7 @@ void handle_chademo_sequence() {
|
|||
|
||||
/* ------------------- State override conditions checks ------------------- */
|
||||
/* ------------------------------------------------------------------------------ */
|
||||
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && !x102_chg_session.s.status.StatusVehicleShifterPosition) {
|
||||
if (CHADEMO_Status >= CHADEMO_EV_ALLOWED && x102_chg_session.s.status.StatusVehicleShifterPosition) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Vehicle is not parked, abort.");
|
||||
#endif
|
||||
|
@ -777,7 +778,6 @@ void handle_chademo_sequence() {
|
|||
#ifdef DEBUG_VIA_USB
|
||||
// Commented unless needed for debug
|
||||
// Serial.println("CHADEMO plug is not inserted.");
|
||||
//
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
@ -1031,9 +1031,18 @@ void handle_chademo_sequence() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Chademo battery selected");
|
||||
#endif
|
||||
|
||||
pinMode(CHADEMO_PIN_2, OUTPUT);
|
||||
digitalWrite(CHADEMO_PIN_2, LOW);
|
||||
pinMode(CHADEMO_PIN_10, OUTPUT);
|
||||
digitalWrite(CHADEMO_PIN_10, LOW);
|
||||
pinMode(CHADEMO_LOCK, OUTPUT);
|
||||
digitalWrite(CHADEMO_LOCK, LOW);
|
||||
pinMode(CHADEMO_PIN_4, INPUT);
|
||||
pinMode(CHADEMO_PIN_7, INPUT);
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "Chademo V2X mode", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
CHADEMO_Status = CHADEMO_IDLE;
|
||||
|
||||
|
@ -1075,6 +1084,9 @@ void setup_battery(void) { // Performs one time setup at startup
|
|||
x109_evse_state.s.status.ChgDischStopControl = 1;
|
||||
|
||||
handle_chademo_sequence();
|
||||
// ISA_deFAULT(); // ISA Setup - it is sufficient to set it once, because it is saved in SUNT
|
||||
// ISA_initialize(); // ISA Setup - it is sufficient to set it once, because it is saved in SUNT
|
||||
// ISA_RESTART();
|
||||
|
||||
setupMillis = millis();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
*
|
||||
* 2024 - Modified to make use of ESP32-Arduino-CAN by miwagner
|
||||
*
|
||||
* 2024.11 - Modified byte sequence to Big Endian (this is the default for IVT) and the same as CHAdeMO
|
||||
* - Fixed and Added send functions
|
||||
* - Added some GET functions
|
||||
* by NJbubo
|
||||
*
|
||||
*/
|
||||
#include "../include.h"
|
||||
#ifdef CHADEMO_BATTERY
|
||||
|
@ -74,16 +79,29 @@ uint16_t get_measured_current() {
|
|||
}
|
||||
|
||||
//This is our CAN interrupt service routine to catch inbound frames
|
||||
inline void ISA_handleFrame(CAN_frame* frame) {
|
||||
void ISA_handleFrame(CAN_frame* frame) {
|
||||
|
||||
if (frame->ID < 0x521 || frame->ID > 0x528) {
|
||||
if (frame->ID < 0x510 || frame->ID > 0x528) {
|
||||
return;
|
||||
}
|
||||
|
||||
framecount++;
|
||||
|
||||
switch (frame->ID) {
|
||||
|
||||
case 0x510:
|
||||
case 0x511:
|
||||
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
||||
Serial.print(" ");
|
||||
Serial.print(frame->ID, HEX);
|
||||
Serial.print(" ");
|
||||
Serial.print(frame->DLC);
|
||||
Serial.print(" ");
|
||||
for (int i = 0; i < frame->DLC; ++i) {
|
||||
Serial.print(frame->data.u8[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
break;
|
||||
|
||||
case 0x521:
|
||||
|
@ -118,7 +136,6 @@ inline void ISA_handleFrame(CAN_frame* frame) {
|
|||
ISA_handle528(frame);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -126,7 +143,7 @@ inline void ISA_handleFrame(CAN_frame* frame) {
|
|||
inline void ISA_handle521(CAN_frame* frame) {
|
||||
long current = 0;
|
||||
current =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
milliamps = current;
|
||||
Amperes = current / 1000.0f;
|
||||
|
@ -135,7 +152,7 @@ inline void ISA_handle521(CAN_frame* frame) {
|
|||
//handle frame for Voltage
|
||||
inline void ISA_handle522(CAN_frame* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
Voltage = volt / 1000.0f;
|
||||
Voltage1 = Voltage - (Voltage2 + Voltage3);
|
||||
|
@ -158,7 +175,7 @@ inline void ISA_handle522(CAN_frame* frame) {
|
|||
//handle frame for Voltage 2
|
||||
inline void ISA_handle523(CAN_frame* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
Voltage2 = volt / 1000.0f;
|
||||
if (Voltage2 > 3)
|
||||
|
@ -177,7 +194,7 @@ inline void ISA_handle523(CAN_frame* frame) {
|
|||
//handle frame for Voltage3
|
||||
inline void ISA_handle524(CAN_frame* frame) {
|
||||
long volt =
|
||||
(long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
(long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
Voltage3 = volt / 1000.0f;
|
||||
|
||||
|
@ -194,7 +211,7 @@ inline void ISA_handle524(CAN_frame* frame) {
|
|||
//handle frame for Temperature
|
||||
inline void ISA_handle525(CAN_frame* frame) {
|
||||
long temp = 0;
|
||||
temp = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
temp = (long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
Temperature = temp / 10;
|
||||
}
|
||||
|
@ -202,14 +219,15 @@ inline void ISA_handle525(CAN_frame* frame) {
|
|||
//handle frame for Kilowatts
|
||||
inline void ISA_handle526(CAN_frame* frame) {
|
||||
watt = 0;
|
||||
watt = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
watt = (long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
|
||||
KW = watt / 1000.0f;
|
||||
}
|
||||
|
||||
//handle frame for Ampere-Hours
|
||||
inline void ISA_handle527(CAN_frame* frame) {
|
||||
As = 0;
|
||||
As = (frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]);
|
||||
As = (long)(frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]);
|
||||
|
||||
AH += (As - lastAs) / 3600.0f;
|
||||
lastAs = As;
|
||||
|
@ -217,133 +235,201 @@ inline void ISA_handle527(CAN_frame* frame) {
|
|||
|
||||
//handle frame for kiloWatt-hours
|
||||
inline void ISA_handle528(CAN_frame* frame) {
|
||||
wh = (long)((frame->data.u8[5] << 24) | (frame->data.u8[4] << 16) | (frame->data.u8[3] << 8) | (frame->data.u8[2]));
|
||||
wh = (long)((frame->data.u8[2] << 24) | (frame->data.u8[3] << 16) | (frame->data.u8[4] << 8) | (frame->data.u8[5]));
|
||||
KWH += (wh - lastWh) / 1000.0f;
|
||||
lastWh = wh;
|
||||
}
|
||||
|
||||
/*
|
||||
void ISA_initialize() {
|
||||
firstframe=false;
|
||||
STOP();
|
||||
delay(700);
|
||||
for(int i=0;i<9;i++) {
|
||||
Serial.println("initialization \n");
|
||||
firstframe = false;
|
||||
ISA_STOP();
|
||||
delay(500);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
Serial.print("ISA Initialization ");
|
||||
Serial.println(i);
|
||||
|
||||
outframe.data.u8[0]=(0x20+i);
|
||||
outframe.data.u8[1]=0x42;
|
||||
outframe.data.u8[2]=0x02;
|
||||
outframe.data.u8[3]=(0x60+(i*18));
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
outframe.data.u8[0] = (0x20 + i);
|
||||
outframe.data.u8[1] = 0x02;
|
||||
outframe.data.u8[2] = 0x02;
|
||||
outframe.data.u8[3] = (0x60 + (i * 18));
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
|
||||
delay(500);
|
||||
|
||||
sendSTORE();
|
||||
delay(500);
|
||||
}
|
||||
|
||||
START();
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
delay(500);
|
||||
lastAs=As;
|
||||
lastWh=wh;
|
||||
}
|
||||
|
||||
ISA_sendSTORE();
|
||||
delay(500);
|
||||
|
||||
ISA_START();
|
||||
delay(500);
|
||||
lastAs = As;
|
||||
lastWh = wh;
|
||||
}
|
||||
|
||||
void ISA_STOP() {
|
||||
outframe.data.u8[0]=0x34;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
Serial.println("ISA STOP");
|
||||
|
||||
outframe.data.u8[0] = 0x34;
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x01;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_sendSTORE() {
|
||||
outframe.data.u8[0]=0x32;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
Serial.println("ISA send STORE");
|
||||
|
||||
outframe.data.u8[0] = 0x32;
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_START() {
|
||||
outframe.data.u8[0]=0x34;
|
||||
outframe.data.u8[1]=0x01;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
Serial.println("ISA START");
|
||||
|
||||
outframe.data.u8[0] = 0x34;
|
||||
outframe.data.u8[1] = 0x01;
|
||||
outframe.data.u8[2] = 0x01;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_RESTART() {
|
||||
//Has the effect of zeroing AH and KWH
|
||||
outframe.data.u8[0]=0x3F;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
//Has the effect of zeroing AH and KWH
|
||||
Serial.println("ISA RESTART");
|
||||
|
||||
outframe.data.u8[0] = 0x3F;
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_deFAULT() {
|
||||
//Returns module to original defaults
|
||||
outframe.data.u8[0]=0x3D;
|
||||
outframe.data.u8[1]=0x00;
|
||||
outframe.data.u8[2]=0x00;
|
||||
outframe.data.u8[3]=0x00;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
//Returns module to original defaults
|
||||
ISA_STOP();
|
||||
delay(500);
|
||||
|
||||
Serial.println("ISA RESTART to default");
|
||||
|
||||
outframe.data.u8[0] = 0x3D;
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
delay(500);
|
||||
|
||||
ISA_START();
|
||||
delay(500);
|
||||
}
|
||||
|
||||
void ISA_initCurrent() {
|
||||
STOP();
|
||||
delay(500);
|
||||
|
||||
Serial.println("initialization \n");
|
||||
|
||||
outframe.data.u8[0]=0x21;
|
||||
outframe.data.u8[1]=0x42;
|
||||
outframe.data.u8[2]=0x01;
|
||||
outframe.data.u8[3]=0x61;
|
||||
outframe.data.u8[4]=0x00;
|
||||
outframe.data.u8[5]=0x00;
|
||||
outframe.data.u8[6]=0x00;
|
||||
outframe.data.u8[7]=0x00;
|
||||
ISA_STOP();
|
||||
delay(500);
|
||||
|
||||
transmit_can((&outframe, can_config.battery);
|
||||
Serial.println("ISA Initialization Current");
|
||||
|
||||
delay(500);
|
||||
outframe.data.u8[0] = 0x21;
|
||||
outframe.data.u8[1] = 0x02;
|
||||
outframe.data.u8[2] = 0x01;
|
||||
outframe.data.u8[3] = 0x61;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
sendSTORE();
|
||||
delay(500);
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
delay(500);
|
||||
|
||||
START();
|
||||
delay(500);
|
||||
lastAs=As;
|
||||
lastWh=wh;
|
||||
ISA_sendSTORE();
|
||||
delay(500);
|
||||
|
||||
ISA_START();
|
||||
delay(500);
|
||||
lastAs = As;
|
||||
lastWh = wh;
|
||||
}
|
||||
*/
|
||||
|
||||
void ISA_getCONFIG(uint8_t i) {
|
||||
Serial.print("ISA Get Config ");
|
||||
Serial.println(i);
|
||||
|
||||
outframe.data.u8[0] = (0x60 + i);
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_getCAN_ID(uint8_t i) {
|
||||
Serial.print("ISA Get CAN ID ");
|
||||
Serial.println(i);
|
||||
|
||||
outframe.data.u8[0] = (0x50 + i);
|
||||
if (i == 8)
|
||||
outframe.data.u8[0] = 0x5D;
|
||||
if (i == 9)
|
||||
outframe.data.u8[0] = 0x5F;
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
|
||||
void ISA_getINFO(uint8_t i) {
|
||||
Serial.print("ISA Get INFO ");
|
||||
Serial.println(i, HEX);
|
||||
|
||||
outframe.data.u8[0] = (0x70 + i);
|
||||
outframe.data.u8[1] = 0x00;
|
||||
outframe.data.u8[2] = 0x00;
|
||||
outframe.data.u8[3] = 0x00;
|
||||
outframe.data.u8[4] = 0x00;
|
||||
outframe.data.u8[5] = 0x00;
|
||||
outframe.data.u8[6] = 0x00;
|
||||
outframe.data.u8[7] = 0x00;
|
||||
|
||||
transmit_can(&outframe, can_config.battery);
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
uint16_t get_measured_voltage();
|
||||
uint16_t get_measured_current();
|
||||
inline void ISA_handler(CAN_frame* frame);
|
||||
void ISA_handleFrame(CAN_frame* frame);
|
||||
inline void ISA_handle521(CAN_frame* frame);
|
||||
inline void ISA_handle522(CAN_frame* frame);
|
||||
inline void ISA_handle523(CAN_frame* frame);
|
||||
|
@ -14,6 +14,16 @@ inline void ISA_handle525(CAN_frame* frame);
|
|||
inline void ISA_handle526(CAN_frame* frame);
|
||||
inline void ISA_handle527(CAN_frame* frame);
|
||||
inline void ISA_handle528(CAN_frame* frame);
|
||||
void ISA_initialize();
|
||||
void ISA_STOP();
|
||||
void ISA_sendSTORE();
|
||||
void ISA_START();
|
||||
void ISA_RESTART();
|
||||
void ISA_deFAULT();
|
||||
void ISA_initCurrent();
|
||||
void ISA_getCONFIG(uint8_t i);
|
||||
void ISA_getCAN_ID(uint8_t i);
|
||||
void ISA_getINFO(uint8_t i);
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -174,11 +174,17 @@ static int16_t battery2_temp_polled_max = 0;
|
|||
static int16_t battery2_temp_polled_min = 0;
|
||||
#endif // DOUBLE_BATTERY
|
||||
|
||||
void print_with_units(char* header, int value, char* units) {
|
||||
Serial.print(header);
|
||||
Serial.print(value);
|
||||
Serial.print(units);
|
||||
}
|
||||
// Clear SOH values
|
||||
static uint8_t stateMachineClearSOH = 0xFF;
|
||||
static uint32_t incomingChallenge = 0xFFFFFFFF;
|
||||
static uint8_t solvedChallenge[8];
|
||||
static bool challengeFailed = false;
|
||||
|
||||
CAN_frame LEAF_CLEAR_SOH = {.FD = false,
|
||||
.ext_ID = false,
|
||||
.DLC = 8,
|
||||
.ID = 0x79B,
|
||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
|
||||
|
||||
void update_values_battery() { /* This function maps all the values fetched via CAN to the correct parameters used for modbus */
|
||||
/* Start with mapping all values */
|
||||
|
@ -197,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
|
||||
|
@ -337,17 +340,18 @@ void update_values_battery() { /* This function maps all the values fetched via
|
|||
datalayer_extended.nissanleaf.HeatingStop = battery_Heating_Stop;
|
||||
datalayer_extended.nissanleaf.HeatingStart = battery_Heating_Start;
|
||||
datalayer_extended.nissanleaf.HeaterSendRequest = battery_Batt_Heater_Mail_Send_Request;
|
||||
datalayer_extended.nissanleaf.CryptoChallenge = incomingChallenge;
|
||||
datalayer_extended.nissanleaf.SolvedChallengeMSB =
|
||||
((solvedChallenge[7] << 24) | (solvedChallenge[6] << 16) | (solvedChallenge[5] << 8) | solvedChallenge[4]);
|
||||
datalayer_extended.nissanleaf.SolvedChallengeLSB =
|
||||
((solvedChallenge[3] << 24) | (solvedChallenge[2] << 16) | (solvedChallenge[1] << 8) | solvedChallenge[0]);
|
||||
datalayer_extended.nissanleaf.challengeFailed = challengeFailed;
|
||||
|
||||
/*Finally print out values to serial if configured to do so*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Values from battery");
|
||||
print_with_units("Real SOC%: ", (battery_SOC * 0.1), "% ");
|
||||
print_with_units(", GIDS: ", battery_GIDS, " (x77Wh) ");
|
||||
print_with_units(", Battery gen: ", LEAF_battery_Type, " ");
|
||||
print_with_units(", Has heater: ", battery_HeatExist, " ");
|
||||
print_with_units(", Max cell voltage: ", battery_min_max_voltage[1], "mV ");
|
||||
print_with_units(", Min cell voltage: ", battery_min_max_voltage[0], "mV ");
|
||||
#endif
|
||||
// Update requests from webserver datalayer
|
||||
if (datalayer_extended.nissanleaf.UserRequestSOHreset) {
|
||||
stateMachineClearSOH = 0; //Start the statemachine
|
||||
datalayer_extended.nissanleaf.UserRequestSOHreset = false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DOUBLE_BATTERY
|
||||
|
@ -369,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
|
||||
|
@ -610,7 +610,7 @@ void receive_can_battery2(CAN_frame rx_frame) {
|
|||
}
|
||||
}
|
||||
|
||||
if (stop_battery_query) { //Leafspy is active, stop our own polling
|
||||
if (stop_battery_query) { //Leafspy/Service request is active, stop our own polling
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -847,6 +847,22 @@ void receive_can_battery(CAN_frame rx_frame) {
|
|||
hold_off_with_polling_10seconds = 10; //Polling is paused for 100s
|
||||
break;
|
||||
case 0x7BB:
|
||||
|
||||
// This section checks if we are doing a SOH reset towards BMS
|
||||
if (stateMachineClearSOH < 255) {
|
||||
//Intercept the messages based on state machine
|
||||
if (rx_frame.data.u8[0] == 0x06) { // Incoming challenge data!
|
||||
// BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF
|
||||
incomingChallenge = ((rx_frame.data.u8[3] << 24) | (rx_frame.data.u8[4] << 16) | (rx_frame.data.u8[5] << 8) |
|
||||
rx_frame.data.u8[6]);
|
||||
}
|
||||
//Error checking
|
||||
if ((rx_frame.data.u8[0] == 0x03) && (rx_frame.data.u8[1] == 0x7F)) {
|
||||
challengeFailed = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//First check which group data we are getting
|
||||
if (rx_frame.data.u8[0] == 0x10) { //First message of a group
|
||||
group_7bb = rx_frame.data.u8[3];
|
||||
|
@ -1127,6 +1143,10 @@ void send_can_battery() {
|
|||
if (currentMillis - previousMillis100 >= INTERVAL_100_MS) {
|
||||
previousMillis100 = currentMillis;
|
||||
|
||||
if (stateMachineClearSOH < 255) { // Enter the ClearSOH statemachine only if we request it
|
||||
clearSOH();
|
||||
}
|
||||
|
||||
//When battery requests heating pack status change, ack this
|
||||
if (battery_Batt_Heater_Mail_Send_Request) {
|
||||
LEAF_50B.data.u8[6] = 0x20; //Batt_Heater_Mail_Send_OK
|
||||
|
@ -1230,10 +1250,189 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib
|
|||
return static_cast<uint16_t>(1094 + (309 - temperature) * 2.5714285714285715);
|
||||
}
|
||||
|
||||
void clearSOH(void) {
|
||||
stop_battery_query = true;
|
||||
hold_off_with_polling_10seconds = 10; // Active battery polling is paused for 100 seconds
|
||||
|
||||
switch (stateMachineClearSOH) {
|
||||
case 0: // Wait until polling actually stops
|
||||
challengeFailed = false;
|
||||
stateMachineClearSOH = 1;
|
||||
break;
|
||||
case 1: // Set CAN_PROCESS_FLAG to 0xC0
|
||||
LEAF_CLEAR_SOH.data = {0x02, 0x10, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// BMS should reply 02 50 C0 FF FF FF FF FF
|
||||
stateMachineClearSOH = 2;
|
||||
break;
|
||||
case 2: // Set something ?
|
||||
LEAF_CLEAR_SOH.data = {0x02, 0x3E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// BMS should reply 7E FF FF FF FF FF FF
|
||||
stateMachineClearSOH = 3;
|
||||
break;
|
||||
case 3: // Request challenge to solve
|
||||
LEAF_CLEAR_SOH.data = {0x02, 0x27, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// BMS should reply with (challenge) 06 67 65 (02 DD 86 43) FF
|
||||
stateMachineClearSOH = 4;
|
||||
break;
|
||||
case 4: // Send back decoded challenge data
|
||||
decodeChallengeData(incomingChallenge, solvedChallenge);
|
||||
LEAF_CLEAR_SOH.data = {
|
||||
0x10, 0x0A, 0x27, 0x66, solvedChallenge[0], solvedChallenge[1], solvedChallenge[2], solvedChallenge[3]};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// BMS should reply 7BB 8 30 01 00 FF FF FF FF FF // Proceed with more data (PID ACK)
|
||||
stateMachineClearSOH = 5;
|
||||
break;
|
||||
case 5: // Reply with even more decoded challenge data
|
||||
LEAF_CLEAR_SOH.data = {
|
||||
0x21, solvedChallenge[4], solvedChallenge[5], solvedChallenge[6], solvedChallenge[7], 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// BMS should reply 02 67 66 FF FF FF FF FF // Thank you for the data
|
||||
stateMachineClearSOH = 6;
|
||||
break;
|
||||
case 6: // Check if solved data was OK
|
||||
LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
//7BB 8 03 71 03 01 FF FF FF FF // If all is well, BMS replies with 03 71 03 01.
|
||||
//Incase you sent wrong challenge, you get 03 7f 31 12
|
||||
stateMachineClearSOH = 7;
|
||||
break;
|
||||
case 7: // Reset SOH% request
|
||||
LEAF_CLEAR_SOH.data = {0x03, 0x31, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
//7BB 8 03 71 03 02 FF FF FF FF // 03 71 03 02 means that BMS accepted command.
|
||||
//7BB 03 7f 31 12 means your challenge was wrong, so command ignored
|
||||
stateMachineClearSOH = 8;
|
||||
break;
|
||||
case 8: // Please proceed with resetting SOH
|
||||
LEAF_CLEAR_SOH.data = {0x02, 0x10, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
transmit_can(&LEAF_CLEAR_SOH, can_config.battery);
|
||||
// 7BB 8 02 50 81 FF FF FF FF FF // SOH reset OK
|
||||
stateMachineClearSOH = 255;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2) {
|
||||
bool bVar1;
|
||||
unsigned int uVar2, uVar3, uVar4, uVar5, uVar6, uVar7, uVar8, uVar9, uVar10, uVar11, iVar12;
|
||||
|
||||
param_1 = param_1 & 0xffff;
|
||||
param_2 = param_2 & 0xffff;
|
||||
uVar10 = 0xffff;
|
||||
iVar12 = 2;
|
||||
do {
|
||||
uVar2 = param_2;
|
||||
if ((param_1 & 1) == 1) {
|
||||
uVar2 = param_1 >> 1;
|
||||
}
|
||||
uVar3 = param_2;
|
||||
if ((param_1 >> 1 & 1) == 1) {
|
||||
uVar3 = param_1 >> 2;
|
||||
}
|
||||
uVar4 = param_2;
|
||||
if ((param_1 >> 2 & 1) == 1) {
|
||||
uVar4 = param_1 >> 3;
|
||||
}
|
||||
uVar5 = param_2;
|
||||
if ((param_1 >> 3 & 1) == 1) {
|
||||
uVar5 = param_1 >> 4;
|
||||
}
|
||||
uVar6 = param_2;
|
||||
if ((param_1 >> 4 & 1) == 1) {
|
||||
uVar6 = param_1 >> 5;
|
||||
}
|
||||
uVar7 = param_2;
|
||||
if ((param_1 >> 5 & 1) == 1) {
|
||||
uVar7 = param_1 >> 6;
|
||||
}
|
||||
uVar11 = param_1 >> 7;
|
||||
uVar8 = param_2;
|
||||
if ((param_1 >> 6 & 1) == 1) {
|
||||
uVar8 = uVar11;
|
||||
}
|
||||
param_1 = param_1 >> 8;
|
||||
uVar9 = param_2;
|
||||
if ((uVar11 & 1) == 1) {
|
||||
uVar9 = param_1;
|
||||
}
|
||||
uVar10 =
|
||||
(((((((((((((((uVar10 & 0x7fff) << 1 ^ uVar2) & 0x7fff) << 1 ^ uVar3) & 0x7fff) << 1 ^ uVar4) & 0x7fff) << 1 ^
|
||||
uVar5) &
|
||||
0x7fff)
|
||||
<< 1 ^
|
||||
uVar6) &
|
||||
0x7fff)
|
||||
<< 1 ^
|
||||
uVar7) &
|
||||
0x7fff)
|
||||
<< 1 ^
|
||||
uVar8) &
|
||||
0x7fff)
|
||||
<< 1 ^
|
||||
uVar9;
|
||||
bVar1 = iVar12 != 1;
|
||||
iVar12 = iVar12 + -1;
|
||||
} while (bVar1);
|
||||
return uVar10;
|
||||
}
|
||||
unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3) {
|
||||
return (param_3 ^ 0x7F88 | param_2 ^ 0x8FE7) * ((param_1 & 0xffff) >> 8 ^ param_1 & 0xff) & 0xffff;
|
||||
}
|
||||
|
||||
short ShortMaskedSumAndProduct(short param_1, short param_2) {
|
||||
unsigned short uVar1;
|
||||
|
||||
uVar1 = param_2 + param_1 * 0x0006 & 0xff;
|
||||
return (uVar1 + param_1) * (uVar1 + param_2);
|
||||
}
|
||||
|
||||
unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2) {
|
||||
unsigned int uVar1;
|
||||
|
||||
param_1 = param_1 & 0xffff;
|
||||
param_2 = param_2 & 0xffff;
|
||||
uVar1 = param_2 & (param_1 | 0x0006) & 0xf;
|
||||
return ((unsigned int)param_1 >> uVar1 | param_1 << (0x10 - uVar1 & 0x1f)) *
|
||||
(param_2 << uVar1 | (unsigned int)param_2 >> (0x10 - uVar1 & 0x1f)) &
|
||||
0xffff;
|
||||
}
|
||||
|
||||
unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3) {
|
||||
unsigned int uVar1, uVar2, iVar3, iVar4;
|
||||
|
||||
uVar1 = MaskedBitwiseRotateMultiply(param_2, param_3);
|
||||
uVar2 = ShortMaskedSumAndProduct(param_2, param_3);
|
||||
uVar1 = ComputeMaskedXorProduct(param_1, uVar1, uVar2);
|
||||
uVar2 = ComputeMaskedXorProduct(param_1, uVar2, uVar1);
|
||||
iVar3 = CyclicXorHash16Bit(uVar1, 0x8421);
|
||||
iVar4 = CyclicXorHash16Bit(uVar2, 0x8421);
|
||||
return iVar4 + iVar3 * 0x10000;
|
||||
}
|
||||
|
||||
void decodeChallengeData(unsigned int incomingChallenge, unsigned char* solvedChallenge) {
|
||||
unsigned int uVar1, uVar2;
|
||||
|
||||
uVar1 = CryptAlgo(0x54e9, 0x3afd, incomingChallenge >> 0x10);
|
||||
uVar2 = CryptAlgo(incomingChallenge & 0xffff, incomingChallenge >> 0x10, 0x54e9);
|
||||
*solvedChallenge = (unsigned char)uVar1;
|
||||
solvedChallenge[1] = (unsigned char)uVar2;
|
||||
solvedChallenge[2] = (unsigned char)((unsigned int)uVar2 >> 8);
|
||||
solvedChallenge[3] = (unsigned char)((unsigned int)uVar1 >> 8);
|
||||
solvedChallenge[4] = (unsigned char)((unsigned int)uVar2 >> 0x10);
|
||||
solvedChallenge[5] = (unsigned char)((unsigned int)uVar1 >> 0x10);
|
||||
solvedChallenge[6] = (unsigned char)((unsigned int)uVar2 >> 0x18);
|
||||
solvedChallenge[7] = (unsigned char)((unsigned int)uVar1 >> 0x18);
|
||||
return;
|
||||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Nissan LEAF battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Nissan LEAF battery", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
datalayer.battery.info.number_of_cells = 96;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
|
|
|
@ -14,5 +14,13 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature);
|
|||
bool is_message_corrupt(CAN_frame rx_frame);
|
||||
void setup_battery(void);
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void clearSOH(void);
|
||||
//Cryptographic functions
|
||||
void decodeChallengeData(unsigned int SeedInput, unsigned char* Crypt_Output_Buffer);
|
||||
unsigned int CyclicXorHash16Bit(unsigned int param_1, unsigned int param_2);
|
||||
unsigned int ComputeMaskedXorProduct(unsigned int param_1, unsigned int param_2, unsigned int param_3);
|
||||
short ShortMaskedSumAndProduct(short param_1, short param_2);
|
||||
unsigned int MaskedBitwiseRotateMultiply(unsigned int param_1, unsigned int param_2);
|
||||
unsigned int CryptAlgo(unsigned int param_1, unsigned int param_2, unsigned int param_3);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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;
|
||||
|
|
324
Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp
Normal file
324
Software/src/battery/RANGE-ROVER-PHEV-BATTERY.cpp
Normal 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
|
18
Software/src/battery/RANGE-ROVER-PHEV-BATTERY.h
Normal file
18
Software/src/battery/RANGE-ROVER-PHEV-BATTERY.h
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -22,8 +22,6 @@ void print_units(char* header, int value, char* units) {
|
|||
|
||||
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
|
||||
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
|
||||
datalayer.battery.status.real_soc = 5000; // 50.00%
|
||||
|
||||
datalayer.battery.status.soh_pptt = 9900; // 99.00%
|
||||
|
@ -40,8 +38,6 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
|||
|
||||
datalayer.battery.status.cell_min_voltage_mV = 3500;
|
||||
|
||||
datalayer.battery.status.active_power_W = 0; // 0W
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = 50; // 5.0*C
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = 60; // 6.0*C
|
||||
|
@ -51,7 +47,7 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
|||
datalayer.battery.status.max_charge_power_W = 5000; // 5kW
|
||||
|
||||
for (int i = 0; i < 97; ++i) {
|
||||
datalayer.battery.status.cell_voltages_mV[i] = 3500 + i;
|
||||
datalayer.battery.status.cell_voltages_mV[i] = 3700 + random(-20, 21);
|
||||
}
|
||||
|
||||
//Fake that we get CAN messages
|
||||
|
@ -73,20 +69,66 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
|||
#endif
|
||||
}
|
||||
|
||||
#ifdef DOUBLE_BATTERY
|
||||
|
||||
void update_values_battery2() { // Handle the values coming in from battery #2
|
||||
|
||||
datalayer.battery2.info.number_of_cells = 96;
|
||||
|
||||
datalayer.battery2.status.real_soc = 5000; // 50.00%
|
||||
|
||||
datalayer.battery2.status.soh_pptt = 9900; // 99.00%
|
||||
|
||||
//datalayer.battery.status.voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
|
||||
|
||||
datalayer.battery2.status.current_dA = 0; // 0 A
|
||||
|
||||
datalayer.battery2.info.total_capacity_Wh = 30000; // 30kWh
|
||||
|
||||
datalayer.battery2.status.remaining_capacity_Wh = 15000; // 15kWh
|
||||
|
||||
datalayer.battery2.status.cell_max_voltage_mV = 3596;
|
||||
|
||||
datalayer.battery2.status.cell_min_voltage_mV = 3500;
|
||||
|
||||
datalayer.battery2.status.temperature_min_dC = 50; // 5.0*C
|
||||
|
||||
datalayer.battery2.status.temperature_max_dC = 60; // 6.0*C
|
||||
|
||||
datalayer.battery2.status.max_discharge_power_W = 5000; // 5kW
|
||||
|
||||
datalayer.battery2.status.max_charge_power_W = 5000; // 5kW
|
||||
|
||||
for (int i = 0; i < 97; ++i) {
|
||||
datalayer.battery2.status.cell_voltages_mV[i] = 3700 + random(-20, 21);
|
||||
}
|
||||
|
||||
//Fake that we get CAN messages
|
||||
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
|
||||
/*Finally print out values to serial if configured to do so*/
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("FAKE Values battery 2 going to inverter");
|
||||
print_units("SOH 2 %: ", (datalayer.battery2.status.soh_pptt * 0.01), "% ");
|
||||
print_units(", SOC 2 %: ", (datalayer.battery2.status.reported_soc * 0.01), "% ");
|
||||
print_units(", Voltage 2: ", (datalayer.battery2.status.voltage_dV * 0.1), "V ");
|
||||
print_units(", Max discharge power 2: ", datalayer.battery2.status.max_discharge_power_W, "W ");
|
||||
print_units(", Max charge power 2: ", datalayer.battery2.status.max_charge_power_W, "W ");
|
||||
print_units(", Max temp 2: ", (datalayer.battery2.status.temperature_max_dC * 0.1), "°C ");
|
||||
print_units(", Min temp 2: ", (datalayer.battery2.status.temperature_min_dC * 0.1), "°C ");
|
||||
print_units(", Max cell voltage 2: ", datalayer.battery2.status.cell_max_voltage_mV, "mV ");
|
||||
print_units(", Min cell voltage 2: ", datalayer.battery2.status.cell_min_voltage_mV, "mV ");
|
||||
Serial.println("");
|
||||
#endif
|
||||
}
|
||||
|
||||
void receive_can_battery2(CAN_frame rx_frame) {
|
||||
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
}
|
||||
#endif // DOUBLE_BATTERY
|
||||
|
||||
void receive_can_battery(CAN_frame rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
// All CAN messages recieved will be logged via serial
|
||||
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.ID, HEX);
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.DLC);
|
||||
Serial.print(" ");
|
||||
for (int i = 0; i < rx_frame.DLC; ++i) {
|
||||
Serial.print(rx_frame.data.u8[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
@ -99,14 +141,15 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Test mode with fake battery selected");
|
||||
#endif
|
||||
randomSeed(analogRead(0));
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV =
|
||||
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
|
||||
datalayer.battery.info.min_design_voltage_dV = 2450; // 245.0V under this, discharging further is disabled
|
||||
|
||||
datalayer.battery.info.number_of_cells = 96;
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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% */
|
||||
|
@ -46,6 +71,9 @@ typedef struct {
|
|||
} DATALAYER_INFO_BMWI3;
|
||||
|
||||
typedef struct {
|
||||
/** bool */
|
||||
/** Which SOC method currently used. 0 = Estimated, 1 = Measured */
|
||||
bool SOC_method = 0;
|
||||
/** uint16_t */
|
||||
/** SOC% estimate. Estimated from total pack voltage */
|
||||
uint16_t SOC_estimated = 0;
|
||||
|
@ -198,6 +226,21 @@ typedef struct {
|
|||
/** bool */
|
||||
/** Heat request sent*/
|
||||
bool HeaterSendRequest = false;
|
||||
/** bool */
|
||||
/** User requesting SOH reset via WebUI*/
|
||||
bool UserRequestSOHreset = false;
|
||||
/** bool */
|
||||
/** True if the crypto challenge response from BMS is signalling a failed attempt*/
|
||||
bool challengeFailed = false;
|
||||
/** uint32_t */
|
||||
/** Cryptographic challenge to be solved */
|
||||
uint32_t CryptoChallenge = 0;
|
||||
/** uint32_t */
|
||||
/** Solution for crypto challenge, MSBs */
|
||||
uint32_t SolvedChallengeMSB = 0;
|
||||
/** uint32_t */
|
||||
/** Solution for crypto challenge, LSBs */
|
||||
uint32_t SolvedChallengeLSB = 0;
|
||||
|
||||
} DATALAYER_INFO_NISSAN_LEAF;
|
||||
|
||||
|
@ -249,6 +292,7 @@ typedef struct {
|
|||
|
||||
class DataLayerExtended {
|
||||
public:
|
||||
DATALAYER_INFO_BMWIX bmwix;
|
||||
DATALAYER_INFO_BMWI3 bmwi3;
|
||||
DATALAYER_INFO_BYDATTO3 bydAtto3;
|
||||
DATALAYER_INFO_CELLPOWER cellpower;
|
||||
|
|
|
@ -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
|
||||
|
|
106
Software/src/devboard/hal/hw_3LB.h
Normal file
106
Software/src/devboard/hal/hw_3LB.h
Normal 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
|
|
@ -66,15 +66,35 @@ SensorConfig sensorConfigs[] = {
|
|||
{"max_charge_power", "Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", "power"},
|
||||
{"bms_status", "BMS Status", "{{ value_json.bms_status }}", "", ""},
|
||||
{"pause_status", "Pause Status", "{{ value_json.pause_status }}", "", ""},
|
||||
|
||||
#ifdef DOUBLE_BATTERY
|
||||
{"SOC_2", "SOC 2 (scaled)", "{{ value_json.SOC_2 }}", "%", "battery"},
|
||||
{"SOC_real_2", "SOC 2 (real)", "{{ value_json.SOC_real_2 }}", "%", "battery"},
|
||||
{"state_of_health_2", "State Of Health 2", "{{ value_json.state_of_health_2 }}", "%", "battery"},
|
||||
{"temperature_min_2", "Temperature Min 2", "{{ value_json.temperature_min_2 }}", "°C", "temperature"},
|
||||
{"temperature_max_2", "Temperature Max 2", "{{ value_json.temperature_max_2 }}", "°C", "temperature"},
|
||||
{"stat_batt_power_2", "Stat Batt Power 2", "{{ value_json.stat_batt_power_2 }}", "W", "power"},
|
||||
{"battery_current_2", "Battery 2 Current", "{{ value_json.battery_current_2 }}", "A", "current"},
|
||||
{"cell_max_voltage_2", "Cell Max Voltage 2", "{{ value_json.cell_max_voltage_2 }}", "V", "voltage"},
|
||||
{"cell_min_voltage_2", "Cell Min Voltage 2", "{{ value_json.cell_min_voltage_2 }}", "V", "voltage"},
|
||||
{"battery_voltage_2", "Battery 2 Voltage", "{{ value_json.battery_voltage_2 }}", "V", "voltage"},
|
||||
{"total_capacity_2", "Battery 2 Total Capacity", "{{ value_json.total_capacity_2 }}", "Wh", "energy"},
|
||||
{"remaining_capacity_2", "Battery 2 Remaining Capacity (scaled)", "{{ value_json.remaining_capacity_2 }}", "Wh",
|
||||
"energy"},
|
||||
{"remaining_capacity_real_2", "Battery 2 Remaining Capacity (real)", "{{ value_json.remaining_capacity_real_2 }}",
|
||||
"Wh", "energy"},
|
||||
{"max_discharge_power_2", "Battery 2 Max Discharge Power", "{{ value_json.max_discharge_power_2 }}", "W", "power"},
|
||||
{"max_charge_power_2", "Battery 2 Max Charge Power", "{{ value_json.max_charge_power_2 }}", "W", "power"},
|
||||
{"bms_status_2", "BMS 2 Status", "{{ value_json.bms_status_2 }}", "", ""},
|
||||
{"pause_status_2", "Pause Status 2", "{{ value_json.pause_status_2 }}", "", ""},
|
||||
#endif // DOUBLE_BATTERY
|
||||
};
|
||||
|
||||
static String generateCommonInfoAutoConfigTopic(const char* object_id) {
|
||||
return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config";
|
||||
}
|
||||
|
||||
static String generateCellVoltageAutoConfigTopic(int cell_number) {
|
||||
return "homeassistant/sensor/" + topic_name + "/cell_voltage" + String(cell_number) + "/config";
|
||||
static String generateCellVoltageAutoConfigTopic(int cell_number, String battery_suffix) {
|
||||
return "homeassistant/sensor/" + topic_name + "/cell_voltage" + battery_suffix + String(cell_number) + "/config";
|
||||
}
|
||||
|
||||
static String generateEventsAutoConfigTopic(const char* object_id) {
|
||||
|
@ -148,7 +168,30 @@ static void publish_common_info(void) {
|
|||
doc["max_discharge_power"] = ((float)datalayer.battery.status.max_discharge_power_W);
|
||||
doc["max_charge_power"] = ((float)datalayer.battery.status.max_charge_power_W);
|
||||
}
|
||||
|
||||
#ifdef DOUBLE_BATTERY
|
||||
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
|
||||
if (datalayer.battery2.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) {
|
||||
doc["SOC_2"] = ((float)datalayer.battery2.status.reported_soc) / 100.0;
|
||||
doc["SOC_real_2"] = ((float)datalayer.battery2.status.real_soc) / 100.0;
|
||||
doc["state_of_health_2"] = ((float)datalayer.battery2.status.soh_pptt) / 100.0;
|
||||
doc["temperature_min_2"] = ((float)((int16_t)datalayer.battery2.status.temperature_min_dC)) / 10.0;
|
||||
doc["temperature_max_2"] = ((float)((int16_t)datalayer.battery2.status.temperature_max_dC)) / 10.0;
|
||||
doc["stat_batt_power_2"] = ((float)((int32_t)datalayer.battery2.status.active_power_W));
|
||||
doc["battery_current_2"] = ((float)((int16_t)datalayer.battery2.status.current_dA)) / 10.0;
|
||||
doc["battery_voltage_2"] = ((float)datalayer.battery2.status.voltage_dV) / 10.0;
|
||||
// publish only if cell voltages have been populated...
|
||||
if (datalayer.battery2.info.number_of_cells != 0u &&
|
||||
datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) {
|
||||
doc["cell_max_voltage_2"] = ((float)datalayer.battery2.status.cell_max_voltage_mV) / 1000.0;
|
||||
doc["cell_min_voltage_2"] = ((float)datalayer.battery2.status.cell_min_voltage_mV) / 1000.0;
|
||||
}
|
||||
doc["total_capacity_2"] = ((float)datalayer.battery2.info.total_capacity_Wh);
|
||||
doc["remaining_capacity_real_2"] = ((float)datalayer.battery2.status.remaining_capacity_Wh);
|
||||
doc["remaining_capacity_2"] = ((float)datalayer.battery2.status.reported_remaining_capacity_Wh);
|
||||
doc["max_discharge_power_2"] = ((float)datalayer.battery2.status.max_discharge_power_W);
|
||||
doc["max_charge_power_2"] = ((float)datalayer.battery2.status.max_charge_power_W);
|
||||
}
|
||||
#endif // DOUBLE_BATTERY
|
||||
serializeJson(doc, mqtt_msg);
|
||||
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
|
@ -167,47 +210,79 @@ static void publish_cell_voltages(void) {
|
|||
#endif // HA_AUTODISCOVERY
|
||||
static JsonDocument doc;
|
||||
static String state_topic = topic_name + "/spec_data";
|
||||
#ifdef DOUBLE_BATTERY
|
||||
static String state_topic_2 = topic_name + "/spec_data_2";
|
||||
|
||||
#endif // DOUBLE_BATTERY
|
||||
|
||||
// If the cell voltage number isn't initialized...
|
||||
if (datalayer.battery.info.number_of_cells == 0u) {
|
||||
return;
|
||||
}
|
||||
#ifdef HA_AUTODISCOVERY
|
||||
if (mqtt_first_transmission == true) {
|
||||
mqtt_first_transmission = false;
|
||||
String topic = "homeassistant/sensor/battery-emulator/cell_voltage";
|
||||
|
||||
for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
|
||||
int cellNumber = i + 1;
|
||||
doc["name"] = "Battery Cell Voltage " + String(cellNumber);
|
||||
doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber);
|
||||
doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber);
|
||||
doc["device_class"] = "voltage";
|
||||
doc["state_class"] = "measurement";
|
||||
doc["state_topic"] = state_topic;
|
||||
doc["unit_of_measurement"] = "V";
|
||||
doc["enabled_by_default"] = true;
|
||||
doc["expire_after"] = 240;
|
||||
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
|
||||
doc["device"]["identifiers"][0] = "battery-emulator";
|
||||
doc["device"]["manufacturer"] = "DalaTech";
|
||||
doc["device"]["model"] = "BatteryEmulator";
|
||||
doc["device"]["name"] = device_name;
|
||||
doc["origin"]["name"] = "BatteryEmulator";
|
||||
doc["origin"]["sw"] = String(version_number) + "-mqtt";
|
||||
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
|
||||
// If the cell voltage number isn't initialized...
|
||||
if (datalayer.battery.info.number_of_cells != 0u) {
|
||||
|
||||
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
|
||||
mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber).c_str(), mqtt_msg, true);
|
||||
for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
|
||||
int cellNumber = i + 1;
|
||||
doc["name"] = "Battery Cell Voltage " + String(cellNumber);
|
||||
doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber);
|
||||
doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber);
|
||||
doc["device_class"] = "voltage";
|
||||
doc["state_class"] = "measurement";
|
||||
doc["state_topic"] = state_topic;
|
||||
doc["unit_of_measurement"] = "V";
|
||||
doc["enabled_by_default"] = true;
|
||||
doc["expire_after"] = 240;
|
||||
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
|
||||
doc["device"]["identifiers"][0] = "battery-emulator";
|
||||
doc["device"]["manufacturer"] = "DalaTech";
|
||||
doc["device"]["model"] = "BatteryEmulator";
|
||||
doc["device"]["name"] = device_name;
|
||||
doc["origin"]["name"] = "BatteryEmulator";
|
||||
doc["origin"]["sw"] = String(version_number) + "-mqtt";
|
||||
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
|
||||
|
||||
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
|
||||
mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true);
|
||||
}
|
||||
doc.clear(); // clear after sending autoconfig
|
||||
}
|
||||
doc.clear(); // clear after sending autoconfig
|
||||
} else {
|
||||
#ifdef DOUBLE_BATTERY
|
||||
// If the cell voltage number isn't initialized...
|
||||
if (datalayer.battery2.info.number_of_cells != 0u) {
|
||||
|
||||
for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
|
||||
int cellNumber = i + 1;
|
||||
doc["name"] = "Battery 2 Cell Voltage " + String(cellNumber);
|
||||
doc["object_id"] = object_id_prefix + "battery_2_voltage_cell" + String(cellNumber);
|
||||
doc["unique_id"] = topic_name + "_battery_2_voltage_cell" + String(cellNumber);
|
||||
doc["device_class"] = "voltage";
|
||||
doc["state_class"] = "measurement";
|
||||
doc["state_topic"] = state_topic_2;
|
||||
doc["unit_of_measurement"] = "V";
|
||||
doc["enabled_by_default"] = true;
|
||||
doc["expire_after"] = 240;
|
||||
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
|
||||
doc["device"]["identifiers"][0] = "battery-emulator";
|
||||
doc["device"]["manufacturer"] = "DalaTech";
|
||||
doc["device"]["model"] = "BatteryEmulator";
|
||||
doc["device"]["name"] = device_name;
|
||||
doc["origin"]["name"] = "BatteryEmulator";
|
||||
doc["origin"]["sw"] = String(version_number) + "-mqtt";
|
||||
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
|
||||
|
||||
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
|
||||
mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true);
|
||||
}
|
||||
doc.clear(); // clear after sending autoconfig
|
||||
}
|
||||
#endif // DOUBLE_BATTERY
|
||||
}
|
||||
#endif // HA_AUTODISCOVERY
|
||||
// If cell voltages haven't been populated...
|
||||
if (datalayer.battery.info.number_of_cells == 0u ||
|
||||
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If cell voltages have been populated...
|
||||
if (datalayer.battery.info.number_of_cells != 0u &&
|
||||
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) {
|
||||
|
||||
JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>();
|
||||
for (size_t i = 0; i < datalayer.battery.info.number_of_cells; ++i) {
|
||||
|
@ -222,9 +297,28 @@ static void publish_cell_voltages(void) {
|
|||
#endif // DEBUG_VIA_USB
|
||||
}
|
||||
doc.clear();
|
||||
#ifdef HA_AUTODISCOVERY
|
||||
}
|
||||
#endif // HA_AUTODISCOVERY
|
||||
|
||||
#ifdef DOUBLE_BATTERY
|
||||
// If cell voltages have been populated...
|
||||
if (datalayer.battery2.info.number_of_cells != 0u &&
|
||||
datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) {
|
||||
|
||||
JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>();
|
||||
for (size_t i = 0; i < datalayer.battery2.info.number_of_cells; ++i) {
|
||||
cell_voltages.add(((float)datalayer.battery2.status.cell_voltages_mV[i]) / 1000.0);
|
||||
}
|
||||
|
||||
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
|
||||
|
||||
if (!mqtt_publish(state_topic_2.c_str(), mqtt_msg, false)) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Cell voltage MQTT msg could not be sent");
|
||||
#endif // DEBUG_VIA_USB
|
||||
}
|
||||
doc.clear();
|
||||
}
|
||||
#endif // DOUBLE_BATTERY
|
||||
}
|
||||
|
||||
void publish_events() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -150,6 +150,7 @@ void init_events(void) {
|
|||
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||
|
@ -281,6 +282,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
|
||||
case EVENT_CAN_INVERTER_MISSING:
|
||||
return "Warning: Inverter not sending messages on CAN bus. Check wiring!";
|
||||
case EVENT_CONTACTOR_WELDED:
|
||||
return "Warning: Contactors sticking/welded. Inspect battery with caution!";
|
||||
case EVENT_CHARGE_LIMIT_EXCEEDED:
|
||||
return "Info: Inverter is charging faster than battery is allowing.";
|
||||
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
||||
|
||||
#define EE_MAGIC_HEADER_VALUE 0x0016 // 0x0000 to 0xFFFF
|
||||
#define EE_MAGIC_HEADER_VALUE 0x0017 // 0x0000 to 0xFFFF
|
||||
|
||||
#define GENERATE_ENUM(ENUM) ENUM,
|
||||
#define GENERATE_STRING(STRING) #STRING,
|
||||
|
@ -37,6 +37,7 @@
|
|||
XX(EVENT_CAN_TX_FAILURE) \
|
||||
XX(EVENT_CAN_INVERTER_MISSING) \
|
||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_CONTACTOR_WELDED) \
|
||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_WATER_INGRESS) \
|
||||
XX(EVENT_12V_LOW) \
|
||||
|
|
|
@ -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>";
|
||||
|
@ -236,6 +275,8 @@ String advanced_battery_processor(const String& var) {
|
|||
#endif //CELLPOWER_BMS
|
||||
|
||||
#ifdef BYD_ATTO_3_BATTERY
|
||||
static const char* SOCmethod[2] = {"Estimated from voltage", "Measured by BMS"};
|
||||
content += "<h4>SOC method used: " + String(SOCmethod[datalayer_extended.bydAtto3.SOC_method]) + "</h4>";
|
||||
content += "<h4>SOC estimated: " + String(datalayer_extended.bydAtto3.SOC_estimated) + "</h4>";
|
||||
content += "<h4>SOC highprec: " + String(datalayer_extended.bydAtto3.SOC_highprec) + "</h4>";
|
||||
content += "<h4>SOC OBD2: " + String(datalayer_extended.bydAtto3.SOC_polled) + "</h4>";
|
||||
|
@ -292,6 +333,11 @@ String advanced_battery_processor(const String& var) {
|
|||
content += "<h4>Heating stopped: " + String(datalayer_extended.nissanleaf.HeatingStop) + "</h4>";
|
||||
content += "<h4>Heating started: " + String(datalayer_extended.nissanleaf.HeatingStart) + "</h4>";
|
||||
content += "<h4>Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "</h4>";
|
||||
content += "<button onclick='askResetSOH()'>Reset degradation data</button>";
|
||||
content += "<h4>CryptoChallenge: " + String(datalayer_extended.nissanleaf.CryptoChallenge) + "</h4>";
|
||||
content += "<h4>SolvedChallenge: " + String(datalayer_extended.nissanleaf.SolvedChallengeMSB) +
|
||||
String(datalayer_extended.nissanleaf.SolvedChallengeLSB) + "</h4>";
|
||||
content += "<h4>Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "</h4>";
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||
|
@ -349,6 +395,15 @@ String advanced_battery_processor(const String& var) {
|
|||
content += "</div>";
|
||||
|
||||
content += "<script>";
|
||||
content +=
|
||||
"function askResetSOH() { if (window.confirm('Are you sure you want to reset degradation data? "
|
||||
"Note this should only be used on 2011-2017 24/30kWh batteries!')) { "
|
||||
"resetSOH(); } }";
|
||||
content += "function resetSOH() {";
|
||||
content += " var xhr = new XMLHttpRequest();";
|
||||
content += " xhr.open('GET', '/resetSOH', true);";
|
||||
content += " xhr.send();";
|
||||
content += "}";
|
||||
content += "function goToMainPage() { window.location.href = '/'; }";
|
||||
content += "</script>";
|
||||
return content;
|
||||
|
|
|
@ -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>";
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "webserver.h"
|
||||
#include <Preferences.h>
|
||||
#include "../../datalayer/datalayer.h"
|
||||
#include "../../datalayer/datalayer_extended.h"
|
||||
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
|
||||
#include "../utils/events.h"
|
||||
#include "../utils/led_handler.h"
|
||||
|
@ -209,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 {
|
||||
|
@ -223,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 {
|
||||
|
@ -231,6 +232,15 @@ void init_webserver() {
|
|||
}
|
||||
});
|
||||
|
||||
// Route for resetting SOH on Nissan LEAF batteries
|
||||
server.on("/resetSOH", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (WEBSERVER_AUTH_REQUIRED && !request->authenticate(http_username, http_password)) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
datalayer_extended.nissanleaf.UserRequestSOHreset = true;
|
||||
request->send(200, "text/plain", "Updated successfully");
|
||||
});
|
||||
|
||||
#ifdef TEST_FAKE_BATTERY
|
||||
// Route for editing FakeBatteryVoltage
|
||||
server.on("/updateFakeBatteryVoltage", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
|
@ -285,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");
|
||||
}
|
||||
|
||||
|
@ -415,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);
|
||||
|
@ -483,114 +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 BYD_KOSTAL_RS485
|
||||
content += "BYD 11kWh HVM battery over Kostal RS485";
|
||||
#endif //BYD_KOSTAL_RS485
|
||||
#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
|
||||
|
@ -652,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;
|
||||
|
||||
|
@ -666,12 +585,16 @@ String processor(const String& var) {
|
|||
content +=
|
||||
formatPowerValue("Scaled Remaining capacity", datalayer.battery.status.reported_remaining_capacity_Wh, "h", 1);
|
||||
|
||||
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);
|
||||
} else {
|
||||
if (datalayer.system.settings.equipment_stop_active) {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red");
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red");
|
||||
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
} else {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
|
||||
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
}
|
||||
|
||||
content += "<h4>Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
|
@ -788,9 +711,22 @@ String processor(const String& var) {
|
|||
content += "<h4 style='color: white;'>Current: " + String(currentFloat, 1) + " A</h4>";
|
||||
content += formatPowerValue("Power", powerFloat, "", 1);
|
||||
content += formatPowerValue("Total capacity", datalayer.battery2.info.total_capacity_Wh, "h", 0);
|
||||
content += formatPowerValue("Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1);
|
||||
content += formatPowerValue("Real Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
|
||||
content +=
|
||||
formatPowerValue("Scaled Remaining capacity", datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1);
|
||||
|
||||
if (datalayer.system.settings.equipment_stop_active) {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1, "red");
|
||||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1, "red");
|
||||
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
} else {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1);
|
||||
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
}
|
||||
|
||||
content += "<h4>Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
content += "<h4>Cell min: " + String(datalayer.battery2.status.cell_min_voltage_mV) + " mV</h4>";
|
||||
if (cell_delta_mv > datalayer.battery2.info.max_cell_voltage_deviation_mV) {
|
||||
|
@ -830,6 +766,11 @@ String processor(const String& var) {
|
|||
content += "<span style='color: red;'>✕</span></h4>";
|
||||
}
|
||||
|
||||
if (emulator_pause_status == NORMAL)
|
||||
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
else
|
||||
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
|
||||
#ifdef CONTACTOR_CONTROL
|
||||
content += "<h4>Contactors controlled by Battery-Emulator: ";
|
||||
if (datalayer.system.status.contactor_control_closed) {
|
||||
|
@ -838,12 +779,28 @@ String processor(const String& var) {
|
|||
content += "<span style='color: red;'>OFF</span>";
|
||||
}
|
||||
content += "</h4>";
|
||||
#endif
|
||||
|
||||
if (emulator_pause_status == NORMAL)
|
||||
content += "<h4>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
else
|
||||
content += "<h4 style='color: red;'>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||
content += "<h4>Pre Charge: ";
|
||||
if (digitalRead(PRECHARGE_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
content += " Cont. Neg.: ";
|
||||
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
|
||||
content += " Cont. Pos.: ";
|
||||
if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) {
|
||||
content += "<span style='color: green;'>✓</span>";
|
||||
} else {
|
||||
content += "<span style='color: red;'>✕</span>";
|
||||
}
|
||||
content += "</h4>";
|
||||
#endif
|
||||
|
||||
content += "</div>";
|
||||
content += "</div>";
|
||||
|
|
|
@ -52,10 +52,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
#define STOP_STATE 0x02
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -35,6 +35,10 @@
|
|||
#include "PYLON-LV-CAN.h"
|
||||
#endif
|
||||
|
||||
#ifdef SCHNEIDER_CAN
|
||||
#include "SCHNEIDER-CAN.h"
|
||||
#endif
|
||||
|
||||
#ifdef SMA_CAN
|
||||
#include "SMA-CAN.h"
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
305
Software/src/inverter/SCHNEIDER-CAN.cpp
Normal file
305
Software/src/inverter/SCHNEIDER-CAN.cpp
Normal file
|
@ -0,0 +1,305 @@
|
|||
#include "../include.h"
|
||||
#ifdef SCHNEIDER_CAN
|
||||
#include "../datalayer/datalayer.h"
|
||||
#include "SCHNEIDER-CAN.h"
|
||||
|
||||
/* Version 2: SE BMS Communication Protocol
|
||||
Protocol: CAN 2.0 Specification
|
||||
Frame: Extended CAN Bus Frame (29 bit identifier)
|
||||
Bitrate: 500 kbps
|
||||
Endian: Big Endian (MSB, most significant byte of a value received first)*/
|
||||
|
||||
/* TODOs
|
||||
- Figure out how to reply with protocol version in 0x320
|
||||
- Figure out what to set Battery Manufacturer ID to in 0x330
|
||||
- Figure out what to set Battery Model ID in 0x330
|
||||
- We will need CAN logs from existing battery OR contact Schneider for one free number
|
||||
*/
|
||||
|
||||
/* Do not change code below unless you are sure what you are doing */
|
||||
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
|
||||
static unsigned long previousMillis2s = 0; // will store last time a 2s CAN Message was send
|
||||
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
|
||||
|
||||
CAN_frame SE_320 = {.FD = false, //SE BMS Protocol Version
|
||||
.ext_ID = true,
|
||||
.DLC = 2,
|
||||
.ID = 0x320,
|
||||
.data = {0x00, 0x02}}; //TODO: How do we reply with Protocol Version: 0x0002 ?
|
||||
CAN_frame SE_321 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x321,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_322 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x322,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_323 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x323,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_324 = {.FD = false, .ext_ID = true, .DLC = 4, .ID = 0x324, .data = {0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_325 = {.FD = false, .ext_ID = true, .DLC = 6, .ID = 0x325, .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_326 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x326,
|
||||
.data = {0x00, STATE_STARTING, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_327 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x327,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_328 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x328,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_330 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x330,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_331 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x331,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_332 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x332,
|
||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
CAN_frame SE_333 = {.FD = false,
|
||||
.ext_ID = true,
|
||||
.DLC = 8,
|
||||
.ID = 0x333,
|
||||
.data = {0x53, 0x45, 0x42, 0x4D, 0x53, 0x00, 0x00, 0x00}}; //SEBMS
|
||||
|
||||
static int16_t temperature_average = 0;
|
||||
static uint16_t remaining_capacity_ah = 0;
|
||||
static uint16_t fully_charged_capacity_ah = 0;
|
||||
static uint16_t commands = 0;
|
||||
static uint16_t warnings = 0;
|
||||
static uint16_t faults = 0;
|
||||
static uint16_t state = 0;
|
||||
|
||||
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||
|
||||
/* Calculate temperature */
|
||||
temperature_average =
|
||||
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
|
||||
|
||||
/* Calculate capacity, Amp hours(Ah) = Watt hours (Wh) / Voltage (V)*/
|
||||
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
|
||||
remaining_capacity_ah =
|
||||
((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * 100);
|
||||
fully_charged_capacity_ah =
|
||||
((datalayer.battery.info.total_capacity_Wh / datalayer.battery.status.voltage_dV) * 100);
|
||||
}
|
||||
/* Set active commands/warnings/faults/state*/
|
||||
if (datalayer.battery.status.bms_status == FAULT) {
|
||||
state = STATE_FAULTED;
|
||||
//TODO: Map warnings and faults incase an event is set. Low prio, but nice to have
|
||||
commands = COMMAND_STOP;
|
||||
} else { //Battery-Emulator running
|
||||
state = STATE_ONLINE;
|
||||
warnings = 0;
|
||||
faults = 0;
|
||||
if (datalayer.battery.status.reported_soc == 10000) {
|
||||
//Battery full. Only allow discharge
|
||||
commands = COMMAND_ONLY_DISCHARGE_ALLOWED;
|
||||
} else if (datalayer.battery.status.reported_soc == 0) {
|
||||
//Battery empty. Only allow charge
|
||||
commands = COMMAND_ONLY_CHARGE_ALLOWED;
|
||||
} else { //SOC is somewhere between 0.1% and 99.9%. Allow both charge and discharge
|
||||
commands = COMMAND_CHARGE_AND_DISCHARGE_ALLOWED;
|
||||
}
|
||||
}
|
||||
|
||||
//Map values to CAN messages
|
||||
//Max charge voltage+2 (eg 10000.00V = 1000000 , 32bits long)
|
||||
SE_321.data.u8[0] = ((datalayer.battery.info.max_design_voltage_dV * 10) >> 24);
|
||||
SE_321.data.u8[1] = (((datalayer.battery.info.max_design_voltage_dV * 10) & 0x00FF0000) >> 16);
|
||||
SE_321.data.u8[2] = (((datalayer.battery.info.max_design_voltage_dV * 10) & 0x0000FF00) >> 8);
|
||||
SE_321.data.u8[3] = ((datalayer.battery.info.max_design_voltage_dV * 10) & 0x000000FF);
|
||||
//Minimum discharge voltage+2 (eg 10000.00V = 1000000 , 32bits long)
|
||||
SE_321.data.u8[4] = ((datalayer.battery.info.min_design_voltage_dV * 10) >> 24);
|
||||
SE_321.data.u8[5] = (((datalayer.battery.info.min_design_voltage_dV * 10) & 0x00FF0000) >> 16);
|
||||
SE_321.data.u8[6] = (((datalayer.battery.info.min_design_voltage_dV * 10) & 0x0000FF00) >> 8);
|
||||
SE_321.data.u8[7] = ((datalayer.battery.info.min_design_voltage_dV * 10) & 0x000000FF);
|
||||
|
||||
//Maximum charge current+2 (eg 10000.00A = 1000000) TODO: Note s32 bit, which direction?
|
||||
SE_322.data.u8[0] = ((datalayer.battery.status.max_charge_current_dA * 10) >> 24);
|
||||
SE_322.data.u8[1] = (((datalayer.battery.status.max_charge_current_dA * 10) & 0x00FF0000) >> 16);
|
||||
SE_322.data.u8[2] = (((datalayer.battery.status.max_charge_current_dA * 10) & 0x0000FF00) >> 8);
|
||||
SE_322.data.u8[3] = ((datalayer.battery.status.max_charge_current_dA * 10) & 0x000000FF);
|
||||
//Maximum discharge current+2 (eg 10000.00A = 1000000) TODO: Note s32 bit, which direction?
|
||||
SE_322.data.u8[4] = ((datalayer.battery.status.max_discharge_current_dA * 10) >> 24);
|
||||
SE_322.data.u8[5] = (((datalayer.battery.status.max_discharge_current_dA * 10) & 0x00FF0000) >> 16);
|
||||
SE_322.data.u8[6] = (((datalayer.battery.status.max_discharge_current_dA * 10) & 0x0000FF00) >> 8);
|
||||
SE_322.data.u8[7] = ((datalayer.battery.status.max_discharge_current_dA * 10) & 0x000000FF);
|
||||
|
||||
//Voltage (ex 370.00 = 37000, 32bits long)
|
||||
SE_323.data.u8[0] = ((datalayer.battery.status.voltage_dV * 10) >> 24);
|
||||
SE_323.data.u8[1] = (((datalayer.battery.status.voltage_dV * 10) & 0x00FF0000) >> 16);
|
||||
SE_323.data.u8[2] = (((datalayer.battery.status.voltage_dV * 10) & 0x0000FF00) >> 8);
|
||||
SE_323.data.u8[3] = ((datalayer.battery.status.voltage_dV * 10) & 0x000000FF);
|
||||
//Current (ex 81.00A = 8100) TODO: Note s32 bit, which direction?
|
||||
SE_323.data.u8[4] = ((datalayer.battery.status.current_dA * 10) >> 24);
|
||||
SE_323.data.u8[5] = (((datalayer.battery.status.current_dA * 10) & 0x00FF0000) >> 16);
|
||||
SE_323.data.u8[6] = (((datalayer.battery.status.current_dA * 10) & 0x0000FF00) >> 8);
|
||||
SE_323.data.u8[7] = ((datalayer.battery.status.current_dA * 10) & 0x000000FF);
|
||||
|
||||
//Temperature average
|
||||
SE_324.data.u8[0] = (temperature_average >> 8);
|
||||
SE_324.data.u8[1] = (temperature_average & 0x00FF);
|
||||
//SOC (100.0%)
|
||||
SE_324.data.u8[2] = ((datalayer.battery.status.reported_soc / 10) >> 8);
|
||||
SE_324.data.u8[3] = ((datalayer.battery.status.reported_soc / 10) & 0x00FF);
|
||||
//Commands (enum)
|
||||
SE_325.data.u8[0] = (commands >> 8);
|
||||
SE_325.data.u8[1] = (commands & 0x00FF);
|
||||
//Warnings (enum)
|
||||
SE_325.data.u8[2] = (warnings >> 8);
|
||||
SE_325.data.u8[3] = (warnings & 0x00FF);
|
||||
//Faults (enum)
|
||||
SE_325.data.u8[4] = (faults >> 8);
|
||||
SE_325.data.u8[5] = (faults & 0x00FF);
|
||||
|
||||
//State (enum)
|
||||
SE_326.data.u8[0] = (state >> 8);
|
||||
SE_326.data.u8[1] = (state & 0x00FF);
|
||||
//Cycle count (OPTIONAL UINT16)
|
||||
//SE_326.data.u8[2] = Cycle count not tracked by emulator
|
||||
//SE_326.data.u8[3] = Cycle count not tracked by emulator
|
||||
//StateOfHealth (OPTIONAL 0-100%)
|
||||
SE_326.data.u8[4] = (datalayer.battery.status.soh_pptt / 100 >> 8);
|
||||
SE_326.data.u8[5] = (datalayer.battery.status.soh_pptt / 100 & 0x00FF);
|
||||
//Capacity (OPTIONAL, full charge) AH+1
|
||||
SE_326.data.u8[6] = (fully_charged_capacity_ah >> 8);
|
||||
SE_326.data.u8[7] = (fully_charged_capacity_ah & 0x00FF);
|
||||
|
||||
//Cell temp max (OPTIONAL dC)
|
||||
SE_327.data.u8[0] = (datalayer.battery.status.temperature_max_dC >> 8);
|
||||
SE_327.data.u8[1] = (datalayer.battery.status.temperature_max_dC & 0x00FF);
|
||||
//Cell temp min (OPTIONAL dC)
|
||||
SE_327.data.u8[2] = (datalayer.battery.status.temperature_min_dC >> 8);
|
||||
SE_327.data.u8[3] = (datalayer.battery.status.temperature_min_dC & 0x00FF);
|
||||
//Cell max volt (OPTIONAL 4.000V)
|
||||
SE_327.data.u8[4] = (datalayer.battery.status.cell_max_voltage_mV >> 8);
|
||||
SE_327.data.u8[5] = (datalayer.battery.status.cell_max_voltage_mV & 0x00FF);
|
||||
//Cell min volt (OPTIONAL 4.000V)
|
||||
SE_327.data.u8[6] = (datalayer.battery.status.cell_min_voltage_mV >> 8);
|
||||
SE_327.data.u8[7] = (datalayer.battery.status.cell_min_voltage_mV & 0x00FF);
|
||||
|
||||
//Lifetime Charge Energy (OPTIONAL, WH, UINT32)
|
||||
//SE_328.data.u8[0] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[1] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[2] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[3] = Lifetime energy not tracked by emulator
|
||||
//Lifetime Discharge Energy (OPTIONAL, WH, UINT32)
|
||||
//SE_328.data.u8[4] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[5] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[6] = Lifetime energy not tracked by emulator
|
||||
//SE_328.data.u8[7] = Lifetime energy not tracked by emulator
|
||||
|
||||
//Battery Manufacturer ID (UINT16)
|
||||
//Unique identifier for each battery manufacturer implementing this protocol. IDs must be requested through Schneider Electric Solar.
|
||||
SE_330.data.u8[0] = 0; //TODO, set Battery Manufacturer ID
|
||||
SE_330.data.u8[1] = 0; //TODO, set Battery Manufacturer ID
|
||||
//Battery Model ID (UINT16)
|
||||
//Unique identifier for each battery model that a manufacturer has implemented this protocol on. IDs must be requested through Schneider Electric Solar.
|
||||
SE_330.data.u8[2] = 0; //TODO, set Battery Model ID
|
||||
SE_330.data.u8[3] = 0; //TODO, set Battery Model ID
|
||||
//Serial numbers
|
||||
//(For instance ABC123 would be represented as:
|
||||
//0x41[char5], 0x42[char4], 0x43[char3], 0x31[char2], 0x32 [char1], 0x33 [char0])
|
||||
SE_330.data.u8[4] = 0x42; //Char 19 - B
|
||||
SE_330.data.u8[5] = 0x41; //Char 18 - A
|
||||
SE_330.data.u8[6] = 0x54; //Char 17 - T
|
||||
SE_330.data.u8[7] = 0x54; //Char 16 - T
|
||||
|
||||
SE_331.data.u8[0] = 0x45; //Char 15 - E
|
||||
SE_331.data.u8[1] = 0x52; //Char 14 - R
|
||||
SE_331.data.u8[2] = 0x59; //Char 13 - Y
|
||||
SE_331.data.u8[3] = 0x45; //Char 12 - E
|
||||
SE_331.data.u8[4] = 0x4D; //Char 11 - M
|
||||
SE_331.data.u8[5] = 0x55; //Char 10 - U
|
||||
SE_331.data.u8[6] = 0x4C; //Char 9 - L
|
||||
SE_331.data.u8[7] = 0x41; //Char 8 - A
|
||||
|
||||
SE_332.data.u8[0] = 0x54; //Char 7 - T
|
||||
SE_332.data.u8[1] = 0x4F; //Char 6 - O
|
||||
SE_332.data.u8[2] = 0x52; //Char 5 - R
|
||||
SE_332.data.u8[3] = 0x30; //Char 4 - 0
|
||||
SE_332.data.u8[4] = 0x31; //Char 3 - 1
|
||||
SE_332.data.u8[5] = 0x32; //Char 2 - 2
|
||||
SE_332.data.u8[6] = 0x33; //Char 1 - 3
|
||||
SE_332.data.u8[7] = 0x34; //Char 0 - 4
|
||||
|
||||
//UNIQUE ID
|
||||
//Schneider Electric Unique string identifier. The value should be an unique string "SEBMS"
|
||||
SE_333.data.u8[0] = 0x53; //Char 5 - S
|
||||
SE_333.data.u8[1] = 0x45; //Char 4 - E
|
||||
SE_333.data.u8[2] = 0x42; //Char 3 - B
|
||||
SE_333.data.u8[3] = 0x4D; //Char 2 - M
|
||||
SE_333.data.u8[4] = 0x53; //Char 1 - S
|
||||
SE_333.data.u8[5] = 0x00; //Char 0 - NULL
|
||||
|
||||
//Protocol version, TODO: How do we reply with protocol version 0x0002 ?
|
||||
SE_320.data.u8[0] = 0x00;
|
||||
SE_320.data.u8[1] = 0x02;
|
||||
}
|
||||
|
||||
void receive_can_inverter(CAN_frame rx_frame) {
|
||||
switch (rx_frame.ID) {
|
||||
case 0x310: // Still alive message from inverter, every 1s
|
||||
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void send_can_inverter() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// Send 500ms CAN Message
|
||||
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
|
||||
previousMillis500ms = currentMillis;
|
||||
|
||||
transmit_can(&SE_321, can_config.inverter);
|
||||
transmit_can(&SE_322, can_config.inverter);
|
||||
transmit_can(&SE_323, can_config.inverter);
|
||||
transmit_can(&SE_324, can_config.inverter);
|
||||
transmit_can(&SE_325, can_config.inverter);
|
||||
}
|
||||
// Send 2s CAN Message
|
||||
if (currentMillis - previousMillis2s >= INTERVAL_2_S) {
|
||||
previousMillis2s = currentMillis;
|
||||
|
||||
transmit_can(&SE_320, can_config.inverter);
|
||||
transmit_can(&SE_326, can_config.inverter);
|
||||
transmit_can(&SE_327, can_config.inverter);
|
||||
}
|
||||
// Send 10s CAN Message
|
||||
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
||||
previousMillis10s = currentMillis;
|
||||
transmit_can(&SE_328, can_config.inverter);
|
||||
transmit_can(&SE_330, can_config.inverter);
|
||||
transmit_can(&SE_331, can_config.inverter);
|
||||
transmit_can(&SE_332, can_config.inverter);
|
||||
transmit_can(&SE_333, can_config.inverter);
|
||||
}
|
||||
}
|
||||
|
||||
void setup_inverter(void) { // Performs one time setup
|
||||
strncpy(datalayer.system.info.inverter_protocol, "Schneider V2 SE BMS CAN", 63);
|
||||
datalayer.system.info.inverter_protocol[63] = '\0';
|
||||
}
|
||||
|
||||
#endif
|
33
Software/src/inverter/SCHNEIDER-CAN.h
Normal file
33
Software/src/inverter/SCHNEIDER-CAN.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef SCHNEIDER_CAN_H
|
||||
#define SCHNEIDER_CAN_H
|
||||
#include "../include.h"
|
||||
|
||||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
#define STATE_OFFLINE 0
|
||||
#define STATE_STANDBY 1
|
||||
#define STATE_STARTING 2
|
||||
#define STATE_ONLINE 3
|
||||
#define STATE_FAULTED 4
|
||||
|
||||
// Same enumerations used for Fault and Warning
|
||||
#define FAULTS_CHARGE_OVERCURRENT 0
|
||||
#define FAULTS_DISCHARGE_OVERCURRENT 1
|
||||
#define FAULTS_OVER_TEMPERATURE 2
|
||||
#define FAULTS_UNDER_TEMPERATURE 3
|
||||
#define FAULTS_OVER_VOLTAGE 4
|
||||
#define FAULTS_UNDER_VOLTAGE 5
|
||||
#define FAULTS_CELL_IMBALANCE 6
|
||||
#define FAULTS_INTERNAL_COM_ERROR 7
|
||||
#define FAULTS_SYSTEM_ERROR 8
|
||||
|
||||
// Commands. Bit0 forced charge request. Bit1 charge permitted. Bit2 discharge permitted. Bit3 Stop
|
||||
#define COMMAND_ONLY_CHARGE_ALLOWED 0x02
|
||||
#define COMMAND_ONLY_DISCHARGE_ALLOWED 0x04
|
||||
#define COMMAND_CHARGE_AND_DISCHARGE_ALLOWED 0x06
|
||||
#define COMMAND_STOP 0x08
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
|
@ -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);
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h"
|
||||
|
||||
void manageSerialLinkTransmitter();
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,5 +8,6 @@
|
|||
#define STOP_STATE 0x02
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
|
||||
void send_tripower_init();
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
#define CAN_INVERTER_SELECTED
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -15,5 +15,6 @@
|
|||
#define UPDATING_FW 4
|
||||
|
||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||
void setup_inverter(void);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue