mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-06 03:50:13 +02:00
Merge branch 'main' into feature/bmw-ix-support
This commit is contained in:
commit
e8346bc6fb
68 changed files with 1440 additions and 764 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
|
||||
|
@ -293,7 +290,7 @@ void core_loop(void* task_time_us) {
|
|||
#ifdef DOUBLE_BATTERY
|
||||
update_values_battery2();
|
||||
#endif
|
||||
update_scaled_values(); // Check if real or calculated SOC% value should be sent
|
||||
update_calculated_values();
|
||||
#ifndef SERIAL_LINK_RECEIVER
|
||||
update_machineryprotection(); // Check safeties (Not on serial link reciever board)
|
||||
#endif
|
||||
|
@ -401,11 +398,11 @@ void init_stored_settings() {
|
|||
}
|
||||
temp = settings.getUInt("MAXCHARGEAMP", false);
|
||||
if (temp != 0) {
|
||||
datalayer.battery.info.max_charge_amp_dA = temp;
|
||||
datalayer.battery.settings.max_user_set_charge_dA = temp;
|
||||
}
|
||||
temp = settings.getUInt("MAXDISCHARGEAMP", false);
|
||||
if (temp != 0) {
|
||||
datalayer.battery.info.max_discharge_amp_dA = temp;
|
||||
datalayer.battery.settings.max_user_set_discharge_dA = temp;
|
||||
temp = settings.getBool("USE_SCALED_SOC", false);
|
||||
datalayer.battery.settings.soc_scaling_active = temp; //This bool needs to be checked inside the temp!= block
|
||||
} // No way to know if it wasnt reset otherwise
|
||||
|
@ -554,29 +551,6 @@ void init_rs485() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void init_inverter() {
|
||||
#ifdef SOLAX_CAN
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
|
||||
intervalUpdateValues = 800; // This protocol also requires the values to be updated faster
|
||||
#endif
|
||||
#ifdef FOXESS_CAN
|
||||
intervalUpdateValues = 950; // This protocol also requires the values to be updated faster
|
||||
#endif
|
||||
#ifdef BYD_SMA
|
||||
datalayer.system.status.inverter_allows_contactor_closing = false; // The inverter needs to allow first
|
||||
pinMode(INVERTER_CONTACTOR_ENABLE_PIN, INPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
void init_battery() {
|
||||
// Inform user what battery is used and perform setup
|
||||
setup_battery();
|
||||
|
||||
#ifdef CHADEMO_BATTERY
|
||||
intervalUpdateValues = 800; // This mode requires the values to be updated faster
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef EQUIPMENT_STOP_BUTTON
|
||||
|
||||
void monitor_equipment_stop_button() {
|
||||
|
@ -614,31 +588,32 @@ void init_equipment_stop_button() {
|
|||
|
||||
#endif
|
||||
|
||||
#ifdef CAN_FD
|
||||
// Functions
|
||||
#ifdef DEBUG_CANFD_DATA
|
||||
enum frameDirection { MSG_RX, MSG_TX };
|
||||
void print_canfd_frame(CANFDMessage rx_frame, frameDirection msgDir); // Needs to be declared before it is defined
|
||||
void print_canfd_frame(CANFDMessage rx_frame, frameDirection msgDir) {
|
||||
int i = 0;
|
||||
(msgDir == 0) ? Serial.print("RX ") : Serial.print("TX ");
|
||||
Serial.print(rx_frame.id, HEX);
|
||||
enum frameDirection { MSG_RX, MSG_TX }; //RX = 0, TX = 1
|
||||
void print_can_frame(CAN_frame frame, frameDirection msgDir);
|
||||
void print_can_frame(CAN_frame frame, frameDirection msgDir) {
|
||||
uint8_t i = 0;
|
||||
Serial.print(millis());
|
||||
Serial.print(" ");
|
||||
for (i = 0; i < rx_frame.len; i++) {
|
||||
Serial.print(rx_frame.data[i] < 16 ? "0" : "");
|
||||
Serial.print(rx_frame.data[i], HEX);
|
||||
(msgDir == 0) ? Serial.print("RX ") : Serial.print("TX ");
|
||||
Serial.print(frame.ID, HEX);
|
||||
Serial.print(" ");
|
||||
Serial.print(frame.DLC);
|
||||
Serial.print(" ");
|
||||
for (i = 0; i < frame.DLC; i++) {
|
||||
Serial.print(frame.data.u8[i] < 16 ? "0" : "");
|
||||
Serial.print(frame.data.u8[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println(" ");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CAN_FD
|
||||
// Functions
|
||||
void receive_canfd() { // This section checks if we have a complete CAN-FD message incoming
|
||||
CANFDMessage frame;
|
||||
if (canfd.available()) {
|
||||
canfd.receive(frame);
|
||||
#ifdef DEBUG_CANFD_DATA
|
||||
print_canfd_frame(frame, frameDirection(MSG_RX));
|
||||
#endif
|
||||
|
||||
CAN_frame rx_frame;
|
||||
rx_frame.ID = frame.id;
|
||||
rx_frame.ext_ID = frame.ext;
|
||||
|
@ -837,7 +812,26 @@ void handle_contactors() {
|
|||
#endif // CONTACTOR_CONTROL
|
||||
}
|
||||
|
||||
void update_scaled_values() {
|
||||
void update_calculated_values() {
|
||||
/* Calculate allowed charge/discharge currents*/
|
||||
if (datalayer.battery.status.voltage_dV > 10) {
|
||||
// Only update value when we have voltage available to avoid div0. TODO: This should be based on nominal voltage
|
||||
datalayer.battery.status.max_charge_current_dA =
|
||||
((datalayer.battery.status.max_charge_power_W * 100) / datalayer.battery.status.voltage_dV);
|
||||
datalayer.battery.status.max_discharge_current_dA =
|
||||
((datalayer.battery.status.max_discharge_power_W * 100) / datalayer.battery.status.voltage_dV);
|
||||
}
|
||||
/* Restrict values from user settings if needed*/
|
||||
if (datalayer.battery.status.max_charge_current_dA > datalayer.battery.settings.max_user_set_charge_dA) {
|
||||
datalayer.battery.status.max_charge_current_dA = datalayer.battery.settings.max_user_set_charge_dA;
|
||||
}
|
||||
if (datalayer.battery.status.max_discharge_current_dA > datalayer.battery.settings.max_user_set_discharge_dA) {
|
||||
datalayer.battery.status.max_discharge_current_dA = datalayer.battery.settings.max_user_set_discharge_dA;
|
||||
}
|
||||
/* Calculate active power based on voltage and current*/
|
||||
datalayer.battery.status.active_power_W =
|
||||
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
|
||||
|
||||
if (datalayer.battery.settings.soc_scaling_active) {
|
||||
/** SOC Scaling
|
||||
*
|
||||
|
@ -882,6 +876,9 @@ void update_scaled_values() {
|
|||
}
|
||||
|
||||
#ifdef DOUBLE_BATTERY
|
||||
/* Calculate active power based on voltage and current*/
|
||||
datalayer.battery2.status.active_power_W =
|
||||
(datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
|
||||
|
||||
// Calculate the scaled remaining capacity in Wh
|
||||
if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery2.status.real_soc > 0) {
|
||||
|
@ -896,7 +893,7 @@ void update_scaled_values() {
|
|||
}
|
||||
#endif
|
||||
|
||||
} else { // No SOC window wanted. Set scaled to same as real.
|
||||
} else { // soc_scaling_active == false. No SOC window wanted. Set scaled to same as real.
|
||||
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
|
||||
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
|
||||
#ifdef DOUBLE_BATTERY
|
||||
|
@ -975,8 +972,8 @@ void storeSettings() {
|
|||
datalayer.battery.settings.max_percentage / 10); // Divide by 10 for backwards compatibility
|
||||
settings.putUInt("MINPERCENTAGE",
|
||||
datalayer.battery.settings.min_percentage / 10); // Divide by 10 for backwards compatibility
|
||||
settings.putUInt("MAXCHARGEAMP", datalayer.battery.info.max_charge_amp_dA);
|
||||
settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.info.max_discharge_amp_dA);
|
||||
settings.putUInt("MAXCHARGEAMP", datalayer.battery.settings.max_user_set_charge_dA);
|
||||
settings.putUInt("MAXDISCHARGEAMP", datalayer.battery.settings.max_user_set_discharge_dA);
|
||||
settings.putBool("USE_SCALED_SOC", datalayer.battery.settings.soc_scaling_active);
|
||||
settings.end();
|
||||
}
|
||||
|
@ -1062,6 +1059,9 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
|
|||
if (!allowed_to_send_CAN) {
|
||||
return;
|
||||
}
|
||||
#ifdef DEBUG_CAN_DATA
|
||||
print_can_frame(*tx_frame, frameDirection(MSG_TX));
|
||||
#endif //DEBUG_CAN_DATA
|
||||
|
||||
switch (interface) {
|
||||
case CAN_NATIVE:
|
||||
|
@ -1104,10 +1104,6 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
|
|||
send_ok = canfd.tryToSend(MCP2518Frame);
|
||||
if (!send_ok) {
|
||||
set_event(EVENT_CANFD_BUFFER_FULL, interface);
|
||||
} else {
|
||||
#ifdef DEBUG_CANFD_DATA
|
||||
print_canfd_frame(MCP2518Frame, frameDirection(MSG_TX));
|
||||
#endif
|
||||
}
|
||||
#else // Interface not compiled, and settings try to use it
|
||||
set_event(EVENT_INTERFACE_MISSING, interface);
|
||||
|
@ -1120,6 +1116,10 @@ void transmit_can(CAN_frame* tx_frame, int interface) {
|
|||
}
|
||||
void receive_can(CAN_frame* rx_frame, int interface) {
|
||||
|
||||
#ifdef DEBUG_CAN_DATA
|
||||
print_can_frame(*rx_frame, frameDirection(MSG_RX));
|
||||
#endif //DEBUG_CAN_DATA
|
||||
|
||||
if (interface == can_config.battery) {
|
||||
receive_can_battery(*rx_frame);
|
||||
}
|
||||
|
|
|
@ -23,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 +43,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 +52,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.
|
||||
|
|
|
@ -58,6 +58,10 @@
|
|||
#include "RJXZS-BMS.h"
|
||||
#endif
|
||||
|
||||
#ifdef RANGE_ROVER_PHEV_BATTERY
|
||||
#include "RANGE-ROVER-PHEV-BATTERY.h"
|
||||
#endif
|
||||
|
||||
#ifdef RENAULT_KANGOO_BATTERY
|
||||
#include "RENAULT-KANGOO-BATTERY.h"
|
||||
#endif
|
||||
|
|
|
@ -239,7 +239,6 @@ static int16_t battery_temperature_max = 0;
|
|||
static int16_t battery_temperature_min = 0;
|
||||
static int16_t battery_max_charge_amperage = 0;
|
||||
static int16_t battery_max_discharge_amperage = 0;
|
||||
static int16_t battery_power = 0;
|
||||
static int16_t battery_current = 0;
|
||||
static uint8_t battery_status_error_isolation_external_Bordnetz = 0;
|
||||
static uint8_t battery_status_error_isolation_internal_Bordnetz = 0;
|
||||
|
@ -308,7 +307,6 @@ static int16_t battery2_temperature_max = 0;
|
|||
static int16_t battery2_temperature_min = 0;
|
||||
static int16_t battery2_max_charge_amperage = 0;
|
||||
static int16_t battery2_max_discharge_amperage = 0;
|
||||
static int16_t battery2_power = 0;
|
||||
static int16_t battery2_current = 0;
|
||||
static uint8_t battery2_status_error_isolation_external_Bordnetz = 0;
|
||||
static uint8_t battery2_status_error_isolation_internal_Bordnetz = 0;
|
||||
|
@ -388,10 +386,6 @@ void update_values_battery2() { //This function maps all the values fetched via
|
|||
datalayer.battery2.status.max_charge_power_W = battery2_BEV_available_power_longterm_charge;
|
||||
}
|
||||
|
||||
battery2_power = (datalayer.battery2.status.current_dA * (datalayer.battery2.status.voltage_dV / 100));
|
||||
|
||||
datalayer.battery2.status.active_power_W = battery2_power;
|
||||
|
||||
datalayer.battery2.status.temperature_min_dC = battery2_temperature_min * 10; // Add a decimal
|
||||
|
||||
datalayer.battery2.status.temperature_max_dC = battery2_temperature_max * 10; // Add a decimal
|
||||
|
@ -456,10 +450,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.max_charge_power_W = battery_BEV_available_power_longterm_charge;
|
||||
|
||||
battery_power = (datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
|
||||
|
||||
datalayer.battery.status.active_power_W = battery_power;
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = battery_temperature_min * 10; // Add a decimal
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = battery_temperature_max * 10; // Add a decimal
|
||||
|
@ -1128,9 +1118,8 @@ void send_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BMW i3 battery selected");
|
||||
#endif //DEBUG_VIA_USB
|
||||
strncpy(datalayer.system.info.battery_protocol, "BMW i3", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
//Before we have started up and detected which battery is in use, use 60AH values
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_60AH;
|
||||
|
@ -1139,9 +1128,6 @@ void setup_battery(void) { // Performs one time setup at startup
|
|||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
|
||||
#ifdef DOUBLE_BATTERY
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Another BMW i3 battery also selected!");
|
||||
#endif //DEBUG_VIA_USB
|
||||
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
|
||||
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
|
||||
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
|
||||
|
|
|
@ -353,7 +353,6 @@ 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 int16_t battery_power = 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
|
||||
|
@ -454,10 +453,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.max_charge_power_W = MAX_CHARGE_POWER_ALLOWED_W;
|
||||
}
|
||||
|
||||
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 = min_battery_temperature;
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = max_battery_temperature;
|
||||
|
@ -784,9 +779,8 @@ void send_can_battery() {
|
|||
//} //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
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("BMW iX battery selected");
|
||||
#endif //DEBUG_VIA_USB
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1031,9 +1031,9 @@ void handle_chademo_sequence() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Chademo battery selected");
|
||||
#endif
|
||||
|
||||
strncpy(datalayer.system.info.battery_protocol, "Chademo V2X mode", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
CHADEMO_Status = CHADEMO_IDLE;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -40,8 +40,6 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
|||
|
||||
datalayer.battery.status.cell_min_voltage_mV = 3500;
|
||||
|
||||
datalayer.battery.status.active_power_W = 0; // 0W
|
||||
|
||||
datalayer.battery.status.temperature_min_dC = 50; // 5.0*C
|
||||
|
||||
datalayer.battery.status.temperature_max_dC = 60; // 6.0*C
|
||||
|
@ -95,8 +93,6 @@ void update_values_battery2() { // Handle the values coming in from battery #2
|
|||
|
||||
datalayer.battery2.status.cell_min_voltage_mV = 3500;
|
||||
|
||||
datalayer.battery2.status.active_power_W = 0; // 0W
|
||||
|
||||
datalayer.battery2.status.temperature_min_dC = 50; // 5.0*C
|
||||
|
||||
datalayer.battery2.status.temperature_max_dC = 60; // 6.0*C
|
||||
|
@ -130,35 +126,11 @@ void update_values_battery2() { // Handle the values coming in from battery #2
|
|||
|
||||
void receive_can_battery2(CAN_frame rx_frame) {
|
||||
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
// All CAN messages recieved will be logged via serial
|
||||
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.ID, HEX);
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.DLC);
|
||||
Serial.print(" ");
|
||||
for (int i = 0; i < rx_frame.DLC; ++i) {
|
||||
Serial.print(rx_frame.data.u8[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
#endif // DOUBLE_BATTERY
|
||||
|
||||
void receive_can_battery(CAN_frame rx_frame) {
|
||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
// All CAN messages recieved will be logged via serial
|
||||
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.ID, HEX);
|
||||
Serial.print(" ");
|
||||
Serial.print(rx_frame.DLC);
|
||||
Serial.print(" ");
|
||||
for (int i = 0; i < rx_frame.DLC; ++i) {
|
||||
Serial.print(rx_frame.data.u8[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
void send_can_battery() {
|
||||
unsigned long currentMillis = millis();
|
||||
|
@ -173,9 +145,8 @@ void send_can_battery() {
|
|||
void setup_battery(void) { // Performs one time setup at startup
|
||||
randomSeed(analogRead(0));
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("Test mode with fake battery selected");
|
||||
#endif
|
||||
strncpy(datalayer.system.info.battery_protocol, "Fake battery for testing purposes", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
|
||||
datalayer.battery.info.max_design_voltage_dV =
|
||||
4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -71,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;
|
||||
|
@ -223,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;
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -275,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>";
|
||||
|
@ -331,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
|
||||
|
@ -388,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) +
|
||||
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 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 BMW_IX_BATTERY
|
||||
content += "BMW iX and i4-7 platform";
|
||||
#endif // BMW_IX_BATTERY
|
||||
#ifdef BYD_ATTO_3_BATTERY
|
||||
content += "BYD Atto 3";
|
||||
#endif // BYD_ATTO_3_BATTERY
|
||||
#ifdef CELLPOWER_BMS
|
||||
content += "Cellpower BMS";
|
||||
#endif // CELLPOWER_BMS
|
||||
#ifdef CHADEMO_BATTERY
|
||||
content += "Chademo V2X mode";
|
||||
#endif // CHADEMO_BATTERY
|
||||
#ifdef IMIEV_CZERO_ION_BATTERY
|
||||
content += "I-Miev / C-Zero / Ion Triplet";
|
||||
#endif // IMIEV_CZERO_ION_BATTERY
|
||||
#ifdef JAGUAR_IPACE_BATTERY
|
||||
content += "Jaguar I-PACE";
|
||||
#endif // JAGUAR_IPACE_BATTERY
|
||||
#ifdef KIA_HYUNDAI_64_BATTERY
|
||||
content += "Kia/Hyundai 64kWh";
|
||||
#endif // KIA_HYUNDAI_64_BATTERY
|
||||
#ifdef KIA_E_GMP_BATTERY
|
||||
content += "Kia/Hyundai EGMP platform";
|
||||
#endif // KIA_E_GMP_BATTERY
|
||||
#ifdef KIA_HYUNDAI_HYBRID_BATTERY
|
||||
content += "Kia/Hyundai Hybrid";
|
||||
#endif // KIA_HYUNDAI_HYBRID_BATTERY
|
||||
#ifdef MG_5_BATTERY
|
||||
content += "MG 5";
|
||||
#endif // MG_5_BATTERY
|
||||
#ifdef NISSAN_LEAF_BATTERY
|
||||
content += "Nissan LEAF";
|
||||
#endif // NISSAN_LEAF_BATTERY
|
||||
#ifdef PYLON_BATTERY
|
||||
content += "Pylon compatible battery";
|
||||
#endif // PYLON_BATTERY
|
||||
#ifdef RJXZS_BMS
|
||||
content += "RJXZS BMS, DIY battery";
|
||||
#endif // RJXZS_BMS
|
||||
#ifdef 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;
|
||||
|
||||
|
@ -669,9 +588,13 @@ String processor(const String& var) {
|
|||
if (emulator_pause_status == NORMAL) {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1);
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1);
|
||||
content += "<h4 style='color: white;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: white;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
} else {
|
||||
content += formatPowerValue("Max discharge power", datalayer.battery.status.max_discharge_power_W, "", 1, "red");
|
||||
content += formatPowerValue("Max charge power", datalayer.battery.status.max_charge_power_W, "", 1, "red");
|
||||
content += "<h4 style='color: red;'>Max discharge current: " + String(maxCurrentDischargeFloat, 1) + " A</h4>";
|
||||
content += "<h4 style='color: red;'>Max charge current: " + String(maxCurrentChargeFloat, 1) + " A</h4>";
|
||||
}
|
||||
|
||||
content += "<h4>Cell max: " + String(datalayer.battery.status.cell_max_voltage_mV) + " mV</h4>";
|
||||
|
|
|
@ -45,10 +45,4 @@
|
|||
#error No battery selected! Choose one from the USER_SETTINGS.h file
|
||||
#endif
|
||||
|
||||
#ifdef KIA_E_GMP_BATTERY
|
||||
#ifndef CAN_FD
|
||||
#error KIA HYUNDAI EGMP BATTERIES CANNOT BE USED WITHOUT CAN FD
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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,
|
||||
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)
|
||||
.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
|
||||
|
|
|
@ -31,6 +31,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