mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
Merge branch 'main' into feature/LEAF-30-reset-SOH
This commit is contained in:
commit
f6861a6b78
19 changed files with 994 additions and 313 deletions
|
@ -53,7 +53,7 @@
|
||||||
|
|
||||||
Preferences settings; // Store user settings
|
Preferences settings; // Store user settings
|
||||||
// The current software version, shown on webserver
|
// The current software version, shown on webserver
|
||||||
const char* version_number = "7.6.dev";
|
const char* version_number = "7.7.dev";
|
||||||
|
|
||||||
// Interval settings
|
// Interval settings
|
||||||
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
|
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
|
||||||
|
@ -85,6 +85,9 @@ uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
|
||||||
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
|
// Create a ModbusRTU server instance listening on Serial2 with 2000ms timeout
|
||||||
ModbusServerRTU MBserver(Serial2, 2000);
|
ModbusServerRTU MBserver(Serial2, 2000);
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
|
||||||
|
#define SERIAL_LINK_BAUDRATE 112500
|
||||||
|
#endif
|
||||||
|
|
||||||
// Common charger parameters
|
// Common charger parameters
|
||||||
volatile float charger_setpoint_HV_VDC = 0.0f;
|
volatile float charger_setpoint_HV_VDC = 0.0f;
|
||||||
|
@ -741,6 +744,7 @@ void handle_contactors() {
|
||||||
if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS ||
|
if (timeSpentInFaultedMode > MAX_ALLOWED_FAULT_TICKS ||
|
||||||
(datalayer.system.settings.equipment_stop_active && contactorStatus != SHUTDOWN_REQUESTED)) {
|
(datalayer.system.settings.equipment_stop_active && contactorStatus != SHUTDOWN_REQUESTED)) {
|
||||||
contactorStatus = SHUTDOWN_REQUESTED;
|
contactorStatus = SHUTDOWN_REQUESTED;
|
||||||
|
datalayer.system.settings.equipment_stop_active = true;
|
||||||
}
|
}
|
||||||
if (contactorStatus == SHUTDOWN_REQUESTED && !datalayer.system.settings.equipment_stop_active) {
|
if (contactorStatus == SHUTDOWN_REQUESTED && !datalayer.system.settings.equipment_stop_active) {
|
||||||
contactorStatus = DISCONNECTED;
|
contactorStatus = DISCONNECTED;
|
||||||
|
@ -856,6 +860,8 @@ void update_scaled_values() {
|
||||||
* Before we use real_soc, we must make sure that it's within the range of min_percentage and max_percentage.
|
* Before we use real_soc, we must make sure that it's within the range of min_percentage and max_percentage.
|
||||||
*/
|
*/
|
||||||
uint32_t calc_soc;
|
uint32_t calc_soc;
|
||||||
|
uint32_t calc_max_capacity;
|
||||||
|
uint32_t calc_reserved_capacity;
|
||||||
// Make sure that the SOC starts out between min and max percentages
|
// Make sure that the SOC starts out between min and max percentages
|
||||||
calc_soc = CONSTRAIN(datalayer.battery.status.real_soc, datalayer.battery.settings.min_percentage,
|
calc_soc = CONSTRAIN(datalayer.battery.status.real_soc, datalayer.battery.settings.min_percentage,
|
||||||
datalayer.battery.settings.max_percentage);
|
datalayer.battery.settings.max_percentage);
|
||||||
|
@ -866,8 +872,6 @@ void update_scaled_values() {
|
||||||
|
|
||||||
// Calculate the scaled remaining capacity in Wh
|
// Calculate the scaled remaining capacity in Wh
|
||||||
if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {
|
if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {
|
||||||
uint32_t calc_max_capacity;
|
|
||||||
uint32_t calc_reserved_capacity;
|
|
||||||
calc_max_capacity = (datalayer.battery.status.remaining_capacity_Wh * 10000 / datalayer.battery.status.real_soc);
|
calc_max_capacity = (datalayer.battery.status.remaining_capacity_Wh * 10000 / datalayer.battery.status.real_soc);
|
||||||
calc_reserved_capacity = calc_max_capacity * datalayer.battery.settings.min_percentage / 10000;
|
calc_reserved_capacity = calc_max_capacity * datalayer.battery.settings.min_percentage / 10000;
|
||||||
// remove % capacity reserved in min_percentage to total_capacity_Wh
|
// remove % capacity reserved in min_percentage to total_capacity_Wh
|
||||||
|
@ -877,9 +881,28 @@ void update_scaled_values() {
|
||||||
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
|
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
|
||||||
|
// Calculate the scaled remaining capacity in Wh
|
||||||
|
if (datalayer.battery2.info.total_capacity_Wh > 0 && datalayer.battery2.status.real_soc > 0) {
|
||||||
|
calc_max_capacity =
|
||||||
|
(datalayer.battery2.status.remaining_capacity_Wh * 10000 / datalayer.battery2.status.real_soc);
|
||||||
|
calc_reserved_capacity = calc_max_capacity * datalayer.battery2.settings.min_percentage / 10000;
|
||||||
|
// remove % capacity reserved in min_percentage to total_capacity_Wh
|
||||||
|
datalayer.battery2.status.reported_remaining_capacity_Wh =
|
||||||
|
datalayer.battery2.status.remaining_capacity_Wh - calc_reserved_capacity;
|
||||||
|
} else {
|
||||||
|
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} else { // No SOC window wanted. Set scaled to same as real.
|
} else { // No SOC window wanted. Set scaled to same as real.
|
||||||
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
|
datalayer.battery.status.reported_soc = datalayer.battery.status.real_soc;
|
||||||
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
|
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery.status.remaining_capacity_Wh;
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
datalayer.battery2.status.reported_soc = datalayer.battery2.status.real_soc;
|
||||||
|
datalayer.battery2.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#ifdef DOUBLE_BATTERY
|
#ifdef DOUBLE_BATTERY
|
||||||
// Perform extra SOC sanity checks on double battery setups
|
// Perform extra SOC sanity checks on double battery setups
|
||||||
|
@ -931,7 +954,7 @@ void runSerialDataLink() {
|
||||||
|
|
||||||
void init_serialDataLink() {
|
void init_serialDataLink() {
|
||||||
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
|
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
|
||||||
Serial2.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
|
Serial2.begin(SERIAL_LINK_BAUDRATE, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,8 @@
|
||||||
//#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus
|
//#define BYD_SMA //Enable this line to emulate a SMA compatible "BYD Battery-Box HVS 10.2KW battery" over CAN bus
|
||||||
//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
|
//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
|
||||||
//#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus
|
//#define FOXESS_CAN //Enable this line to emulate a "HV2600/ECS4100 battery" over CAN bus
|
||||||
//#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus
|
//#define PYLON_LV_CAN //Enable this line to emulate a "48V Pylontech battery" over CAN bus
|
||||||
|
//#define PYLON_CAN //Enable this line to emulate a "High Voltage Pylontech battery" over CAN bus
|
||||||
//#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus
|
//#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus
|
||||||
//#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus
|
//#define SMA_TRIPOWER_CAN //Enable this line to emulate a "SMA Home Storage battery" over CAN bus
|
||||||
//#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus
|
//#define SOFAR_CAN //Enable this line to emulate a "Sofar Energy Storage Inverter High Voltage BMS General Protocol (Extended Frame)" over CAN bus
|
||||||
|
@ -108,6 +109,10 @@
|
||||||
#define BATTERY_MAXPERCENTAGE 8000
|
#define BATTERY_MAXPERCENTAGE 8000
|
||||||
// 2000 = 20.0% , Min percentage the battery will discharge to (Inverter gets 0% when reached)
|
// 2000 = 20.0% , Min percentage the battery will discharge to (Inverter gets 0% when reached)
|
||||||
#define BATTERY_MINPERCENTAGE 2000
|
#define BATTERY_MINPERCENTAGE 2000
|
||||||
|
// 500 = 50.0 °C , Max temperature (Will produce a battery overheat event if above)
|
||||||
|
#define BATTERY_MAXTEMPERATURE 500
|
||||||
|
// -250 = -25.0 °C , Min temperature (Will produce a battery frozen event if below)
|
||||||
|
#define BATTERY_MINTEMPERATURE -250
|
||||||
// 300 = 30.0A , BYD CAN specific setting, Max charge in Amp (Some inverters needs to be limited)
|
// 300 = 30.0A , BYD CAN specific setting, Max charge in Amp (Some inverters needs to be limited)
|
||||||
#define BATTERY_MAX_CHARGE_AMP 300
|
#define BATTERY_MAX_CHARGE_AMP 300
|
||||||
// 300 = 30.0A , BYD CAN specific setting, Max discharge in Amp (Some inverters needs to be limited)
|
// 300 = 30.0A , BYD CAN specific setting, Max discharge in Amp (Some inverters needs to be limited)
|
||||||
|
|
|
@ -422,14 +422,20 @@ void update_values_battery2() { //This function maps all the values fetched via
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_HVIL_FAILURE);
|
clear_event(EVENT_HVIL_FAILURE);
|
||||||
}
|
}
|
||||||
if (battery2_status_precharge_locked == 2) { // Capacitor seated?
|
if (battery2_status_error_disconnecting_switch > 0) { // Check if contactors are sticking / welded
|
||||||
set_event(EVENT_PRECHARGE_FAILURE, 2);
|
set_event(EVENT_CONTACTOR_WELDED, 0);
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_PRECHARGE_FAILURE);
|
clear_event(EVENT_CONTACTOR_WELDED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
|
void update_values_battery() { //This function maps all the values fetched via CAN to the battery datalayer
|
||||||
|
if (datalayer.system.settings.equipment_stop_active == true) {
|
||||||
|
digitalWrite(WUP_PIN, LOW); // Turn off WUP_PIN
|
||||||
|
} else {
|
||||||
|
digitalWrite(WUP_PIN, HIGH); // Wake up the battery
|
||||||
|
}
|
||||||
|
|
||||||
if (!battery_awake) {
|
if (!battery_awake) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -484,10 +490,10 @@ void update_values_battery() { //This function maps all the values fetched via
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_HVIL_FAILURE);
|
clear_event(EVENT_HVIL_FAILURE);
|
||||||
}
|
}
|
||||||
if (battery_status_precharge_locked == 2) { // Capacitor seated?
|
if (battery_status_error_disconnecting_switch > 0) { // Check if contactors are sticking / welded
|
||||||
set_event(EVENT_PRECHARGE_FAILURE, 0);
|
set_event(EVENT_CONTACTOR_WELDED, 0);
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_PRECHARGE_FAILURE);
|
clear_event(EVENT_CONTACTOR_WELDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update webserver datalayer
|
// Update webserver datalayer
|
||||||
|
|
|
@ -6,13 +6,7 @@
|
||||||
#include "KIA-E-GMP-BATTERY.h"
|
#include "KIA-E-GMP-BATTERY.h"
|
||||||
|
|
||||||
/* Do not change code below unless you are sure what you are doing */
|
/* Do not change code below unless you are sure what you are doing */
|
||||||
static unsigned long previousMillis10ms = 0; // will store last time a 10ms CAN Message was send
|
|
||||||
static unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send
|
|
||||||
static unsigned long previousMillis30ms = 0; // will store last time a 30ms CAN Message was send
|
|
||||||
static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send
|
|
||||||
static unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send
|
static unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send
|
||||||
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
|
|
||||||
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
|
|
||||||
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
|
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
|
||||||
|
|
||||||
const unsigned char crc8_table[256] =
|
const unsigned char crc8_table[256] =
|
||||||
|
@ -63,7 +57,7 @@ static uint8_t KIA_7E4_COUNTER = 0x01;
|
||||||
static int8_t temperature_water_inlet = 0;
|
static int8_t temperature_water_inlet = 0;
|
||||||
static int8_t powerRelayTemperature = 0;
|
static int8_t powerRelayTemperature = 0;
|
||||||
static int8_t heatertemp = 0;
|
static int8_t heatertemp = 0;
|
||||||
|
static bool set_voltage_limits = false;
|
||||||
static uint8_t ticks_200ms_counter = 0;
|
static uint8_t ticks_200ms_counter = 0;
|
||||||
static uint8_t EGMP_1CF_counter = 0;
|
static uint8_t EGMP_1CF_counter = 0;
|
||||||
static uint8_t EGMP_3XF_counter = 0;
|
static uint8_t EGMP_3XF_counter = 0;
|
||||||
|
@ -582,208 +576,7 @@ CAN_frame* messages[] = {&message_1, &message_2, &message_3, &message_4, &me
|
||||||
&message_43, &message_44, &message_45, &message_46, &message_47, &message_48, &message_49,
|
&message_43, &message_44, &message_45, &message_46, &message_47, &message_48, &message_49,
|
||||||
&message_50, &message_51, &message_52, &message_53, &message_54, &message_55, &message_56,
|
&message_50, &message_51, &message_52, &message_53, &message_54, &message_55, &message_56,
|
||||||
&message_57, &message_58, &message_59, &message_60, &message_61, &message_62, &message_63};
|
&message_57, &message_58, &message_59, &message_60, &message_61, &message_62, &message_63};
|
||||||
|
/* PID polling messages */
|
||||||
/* These messages are rest of the vehicle messages, to reduce number of active fault codes */
|
|
||||||
CAN_frame EGMP_1CF = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x1CF,
|
|
||||||
.data = {0x56, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_3AA = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3AA,
|
|
||||||
.data = {0xFF, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_3E0 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3E0,
|
|
||||||
.data = {0xC3, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_3E1 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3E1,
|
|
||||||
.data = {0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_36F = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x36F,
|
|
||||||
.data = {0x28, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_37F = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x37F,
|
|
||||||
.data = {0x9B, 0x30, 0x52, 0x24, 0x41, 0x02, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4B4 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4B4,
|
|
||||||
.data = {0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4B5 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4B5,
|
|
||||||
.data = {0x08, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4B7 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4B7,
|
|
||||||
.data = {0x08, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4CC = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4CC,
|
|
||||||
.data = {0x08, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4CE = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4CE,
|
|
||||||
.data = {0x16, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
|
|
||||||
CAN_frame EGMP_4D8 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4D8,
|
|
||||||
.data = {0x40, 0x10, 0xF0, 0xF0, 0x40, 0xF2, 0x1E, 0xCC}};
|
|
||||||
CAN_frame EGMP_4DD = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4DD,
|
|
||||||
.data = {0x3F, 0xFC, 0xFF, 0x00, 0x38, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4E7 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4E7,
|
|
||||||
.data = {0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00}};
|
|
||||||
CAN_frame EGMP_4E9 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4E9,
|
|
||||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4EA = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4EA,
|
|
||||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4EB = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4EB,
|
|
||||||
.data = {0x01, 0x50, 0x0B, 0x26, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4EC = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4EC,
|
|
||||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F}};
|
|
||||||
CAN_frame EGMP_4ED = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4ED,
|
|
||||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F}};
|
|
||||||
CAN_frame EGMP_4EE = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4EE,
|
|
||||||
.data = {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4EF = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4EF,
|
|
||||||
.data = {0x2B, 0xFE, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_405 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x405,
|
|
||||||
.data = {0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_410 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x410,
|
|
||||||
.data = {0xA6, 0x10, 0xFF, 0x3C, 0xFF, 0x7F, 0xFF, 0xFF}};
|
|
||||||
CAN_frame EGMP_411 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x411,
|
|
||||||
.data = {0xEA, 0x22, 0x50, 0x51, 0x00, 0x00, 0x00, 0x40}};
|
|
||||||
CAN_frame EGMP_412 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x412,
|
|
||||||
.data = {0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_413 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x413,
|
|
||||||
.data = {0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_414 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x414,
|
|
||||||
.data = {0xF0, 0x10, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_416 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x416,
|
|
||||||
.data = {0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_417 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x417,
|
|
||||||
.data = {0xC7, 0x10, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_418 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x418,
|
|
||||||
.data = {0x17, 0x20, 0x00, 0x00, 0x14, 0x0C, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_3C1 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3C1,
|
|
||||||
.data = {0x59, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_3C2 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3C2,
|
|
||||||
.data = {0x07, 0x00, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4F0 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4F0,
|
|
||||||
.data = {0x8A, 0x0A, 0x0D, 0x34, 0x60, 0x18, 0x12, 0xFC}};
|
|
||||||
CAN_frame EGMP_4F2 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4F2,
|
|
||||||
.data = {0x0A, 0xC3, 0xD5, 0xFF, 0x0F, 0x21, 0x80, 0x2B}};
|
|
||||||
CAN_frame EGMP_4FE = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4FE,
|
|
||||||
.data = {0x69, 0x3F, 0x00, 0x04, 0xDF, 0x01, 0x4C, 0xA8}};
|
|
||||||
CAN_frame EGMP_48F = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x48F,
|
|
||||||
.data = {0xAD, 0x10, 0x41, 0x00, 0x05, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_419 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x419,
|
|
||||||
.data = {0xC7, 0x90, 0xB9, 0xD2, 0x0D, 0x62, 0x7A, 0x00}};
|
|
||||||
CAN_frame EGMP_422 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x422,
|
|
||||||
.data = {0x15, 0x10, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_444 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x444,
|
|
||||||
.data = {0x96, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_641 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x641,
|
|
||||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF}};
|
|
||||||
CAN_frame EGMP_7E4 = {.FD = true,
|
CAN_frame EGMP_7E4 = {.FD = true,
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
|
@ -804,6 +597,32 @@ void set_cell_voltages(CAN_frame rx_frame, int start, int length, int startCell)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_voltage_minmax_limits() {
|
||||||
|
|
||||||
|
uint8_t valid_cell_count = 0;
|
||||||
|
for (int i = 0; i < MAX_AMOUNT_CELLS; ++i) {
|
||||||
|
if (datalayer.battery.status.cell_voltages_mV[i] > 0) {
|
||||||
|
++valid_cell_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (valid_cell_count == 144) {
|
||||||
|
datalayer.battery.info.number_of_cells = valid_cell_count;
|
||||||
|
datalayer.battery.info.max_design_voltage_dV = 6048;
|
||||||
|
datalayer.battery.info.min_design_voltage_dV = 4320;
|
||||||
|
} else if (valid_cell_count == 180) {
|
||||||
|
datalayer.battery.info.number_of_cells = valid_cell_count;
|
||||||
|
datalayer.battery.info.max_design_voltage_dV = 7560;
|
||||||
|
datalayer.battery.info.min_design_voltage_dV = 5400;
|
||||||
|
} else if (valid_cell_count == 192) {
|
||||||
|
datalayer.battery.info.number_of_cells = valid_cell_count;
|
||||||
|
datalayer.battery.info.max_design_voltage_dV = 8064;
|
||||||
|
datalayer.battery.info.min_design_voltage_dV = 5760;
|
||||||
|
} else {
|
||||||
|
// We are still starting up? Not all cells available.
|
||||||
|
set_voltage_limits = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
|
static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
|
||||||
uint8_t crc = initial_value;
|
uint8_t crc = initial_value;
|
||||||
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
|
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
|
||||||
|
@ -826,8 +645,16 @@ void update_values_battery() { //This function maps all the values fetched via
|
||||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
//datalayer.battery.status.max_charge_power_W = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts
|
//datalayer.battery.status.max_charge_power_W = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts
|
||||||
//The allowed charge power is not available. We hardcode this value for now
|
//The allowed charge power is not available. We estimate this value for now
|
||||||
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
if (datalayer.battery.status.real_soc > 9900) {
|
||||||
|
datalayer.battery.status.max_charge_power_W = 0;
|
||||||
|
} else if (datalayer.battery.status.real_soc >
|
||||||
|
RAMPDOWN_SOC) { // When real SOC is between 90-99%, ramp the value between Max<->0
|
||||||
|
datalayer.battery.status.max_charge_power_W =
|
||||||
|
RAMPDOWNPOWERALLOWED * (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
||||||
|
} else { // No limits, max charging power allowed
|
||||||
|
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
|
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
|
||||||
//The allowed discharge power is not available. We hardcode this value for now
|
//The allowed discharge power is not available. We hardcode this value for now
|
||||||
|
@ -845,6 +672,11 @@ void update_values_battery() { //This function maps all the values fetched via
|
||||||
|
|
||||||
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
|
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
|
||||||
|
|
||||||
|
if ((millis() > INTERVAL_60_S) && !set_voltage_limits) {
|
||||||
|
set_voltage_limits = true;
|
||||||
|
set_voltage_minmax_limits(); // Count cells, and set voltage limits accordingly
|
||||||
|
}
|
||||||
|
|
||||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||||
set_event(EVENT_CANFD_RX_FAILURE, 0);
|
set_event(EVENT_CANFD_RX_FAILURE, 0);
|
||||||
|
@ -1178,16 +1010,16 @@ void send_can_battery() {
|
||||||
messageIndex = 0;
|
messageIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Send 500ms CANFD message
|
//Send 200ms CANFD message
|
||||||
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
|
if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) {
|
||||||
previousMillis500ms = currentMillis;
|
previousMillis200ms = currentMillis;
|
||||||
// Check if sending of CAN messages has been delayed too much.
|
// Check if sending of CAN messages has been delayed too much.
|
||||||
if ((currentMillis - previousMillis500ms >= INTERVAL_500_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
if ((currentMillis - previousMillis200ms >= INTERVAL_200_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis500ms));
|
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200ms));
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_CAN_OVERRUN);
|
clear_event(EVENT_CAN_OVERRUN);
|
||||||
}
|
}
|
||||||
previousMillis500ms = currentMillis;
|
previousMillis200ms = currentMillis;
|
||||||
|
|
||||||
EGMP_7E4.data.u8[3] = KIA_7E4_COUNTER;
|
EGMP_7E4.data.u8[3] = KIA_7E4_COUNTER;
|
||||||
|
|
||||||
|
@ -1200,19 +1032,10 @@ void send_can_battery() {
|
||||||
KIA_7E4_COUNTER = 0x01;
|
KIA_7E4_COUNTER = 0x01;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Send 1s CANFD message
|
|
||||||
if (currentMillis - previousMillis1s >= INTERVAL_1_S) {
|
|
||||||
previousMillis1s = currentMillis;
|
|
||||||
/* COMMENTED OUT WHILE CONTACTOR CLOSING TESTING
|
|
||||||
transmit_can(&EGMP_48F, can_config.battery);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
//Send 10s CANFD message
|
//Send 10s CANFD message
|
||||||
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
||||||
previousMillis10s = currentMillis;
|
previousMillis10s = currentMillis;
|
||||||
/* COMMENTED OUT WHILE CONTACTOR CLOSING TESTING
|
|
||||||
transmit_can(&EGMP_4FE, can_config.battery);
|
|
||||||
*/
|
|
||||||
ok_start_polling_battery = true;
|
ok_start_polling_battery = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1232,6 +1055,7 @@ void setup_battery(void) { // Performs one time setup at startup
|
||||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
||||||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
||||||
|
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -14,6 +14,8 @@ extern ACAN2517FD canfd;
|
||||||
#define MIN_CELL_VOLTAGE_MV 2950 //Battery is put into emergency stop if one cell goes below this value
|
#define MIN_CELL_VOLTAGE_MV 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||||
#define MAXCHARGEPOWERALLOWED 10000
|
#define MAXCHARGEPOWERALLOWED 10000
|
||||||
#define MAXDISCHARGEPOWERALLOWED 10000
|
#define MAXDISCHARGEPOWERALLOWED 10000
|
||||||
|
#define RAMPDOWN_SOC 9000 // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
|
#define RAMPDOWNPOWERALLOWED 10000 // What power we ramp down from towards top balancing
|
||||||
|
|
||||||
void setup_battery(void);
|
void setup_battery(void);
|
||||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||||
|
|
|
@ -67,6 +67,9 @@ void update_values_battery() {
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10));
|
datalayer.battery.status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10));
|
||||||
|
|
||||||
|
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||||
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV;
|
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV;
|
||||||
|
|
||||||
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV;
|
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV;
|
||||||
|
|
|
@ -1,23 +1,69 @@
|
||||||
#include "../include.h"
|
#include "../include.h"
|
||||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||||
#include "../datalayer/datalayer.h"
|
#include "../datalayer/datalayer.h"
|
||||||
|
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
||||||
|
|
||||||
/* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component
|
/* TODO
|
||||||
|
- Add //NVROL Reset
|
||||||
|
- Add //Enable temporisation before sleep (see ljames28 repo)
|
||||||
|
|
||||||
|
"If the pack is in a state where it is confused about the time, you may need to reset it's NVROL memory.
|
||||||
|
However, if the power is later power cycled, it will revert back to his previous confused state.
|
||||||
|
Therefore, after resetting the NVROL you must enable "temporisation before sleep", and then stop streaming 373.
|
||||||
|
It will then save the data and go to sleep. When the pack is confused, the state of charge may reset back to incorrect value
|
||||||
|
every time the power is reset which can be dangerous. In this state, the voltage will still be accurate"
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Information in this file is based on:
|
||||||
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp
|
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp
|
||||||
|
https://github.com/ljames28/Renault-Zoe-PH2-ZE50-Canbus-LBC-Information?tab=readme-ov-file
|
||||||
|
https://github.com/fesch/CanZE/tree/master/app/src/main/assets/ZOE_Ph2
|
||||||
/*
|
/*
|
||||||
|
|
||||||
/* Do not change code below unless you are sure what you are doing */
|
/* Do not change code below unless you are sure what you are doing */
|
||||||
static uint16_t LB_SOC = 50;
|
static uint16_t battery_soc = 0;
|
||||||
static uint16_t LB_SOH = 99;
|
static uint16_t battery_usable_soc = 5000;
|
||||||
static int16_t LB_Average_Temperature = 0;
|
static uint16_t battery_soh = 10000;
|
||||||
static uint32_t LB_Charge_Power_W = 0;
|
static uint16_t battery_pack_voltage = 370;
|
||||||
static int32_t LB_Current = 0;
|
static uint16_t battery_max_cell_voltage = 3700;
|
||||||
static uint16_t LB_kWh_Remaining = 0;
|
static uint16_t battery_min_cell_voltage = 3700;
|
||||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
static uint16_t battery_12v = 0;
|
||||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
static uint16_t battery_avg_temp = 920;
|
||||||
static uint16_t LB_Battery_Voltage = 3700;
|
static uint16_t battery_min_temp = 920;
|
||||||
|
static uint16_t battery_max_temp = 920;
|
||||||
|
static uint16_t battery_max_power = 0;
|
||||||
|
static uint16_t battery_interlock = 0;
|
||||||
|
static uint16_t battery_kwh = 0;
|
||||||
|
static int32_t battery_current = 32640;
|
||||||
|
static uint16_t battery_current_offset = 0;
|
||||||
|
static uint16_t battery_max_generated = 0;
|
||||||
|
static uint16_t battery_max_available = 0;
|
||||||
|
static uint16_t battery_current_voltage = 0;
|
||||||
|
static uint16_t battery_charging_status = 0;
|
||||||
|
static uint16_t battery_remaining_charge = 0;
|
||||||
|
static uint16_t battery_balance_capacity_total = 0;
|
||||||
|
static uint16_t battery_balance_time_total = 0;
|
||||||
|
static uint16_t battery_balance_capacity_sleep = 0;
|
||||||
|
static uint16_t battery_balance_time_sleep = 0;
|
||||||
|
static uint16_t battery_balance_capacity_wake = 0;
|
||||||
|
static uint16_t battery_balance_time_wake = 0;
|
||||||
|
static uint16_t battery_bms_state = 0;
|
||||||
|
static uint16_t battery_balance_switches = 0;
|
||||||
|
static uint16_t battery_energy_complete = 0;
|
||||||
|
static uint16_t battery_energy_partial = 0;
|
||||||
|
static uint16_t battery_slave_failures = 0;
|
||||||
|
static uint16_t battery_mileage = 0;
|
||||||
|
static uint16_t battery_fan_speed = 0;
|
||||||
|
static uint16_t battery_fan_period = 0;
|
||||||
|
static uint16_t battery_fan_control = 0;
|
||||||
|
static uint16_t battery_fan_duty = 0;
|
||||||
|
static uint16_t battery_temporisation = 0;
|
||||||
|
static uint16_t battery_time = 0;
|
||||||
|
static uint16_t battery_pack_time = 0;
|
||||||
|
static uint16_t battery_soc_min = 0;
|
||||||
|
static uint16_t battery_soc_max = 0;
|
||||||
|
|
||||||
CAN_frame ZOE_373 = {.FD = false,
|
CAN_frame ZOE_373 = {.FD = false,
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
|
@ -28,36 +74,151 @@ CAN_frame ZOE_POLL_18DADBF1 = {.FD = false,
|
||||||
.ext_ID = true,
|
.ext_ID = true,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x18DADBF1,
|
.ID = 0x18DADBF1,
|
||||||
.data = {0x03, 0x22, 0x90, 0x00, 0xff, 0xff, 0xff, 0xff}};
|
.data = {0x03, 0x22, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
|
//NVROL Reset
|
||||||
|
CAN_frame ZOE_NVROL_1_18DADBF1 = {.FD = false,
|
||||||
|
.ext_ID = true,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x18DADBF1,
|
||||||
|
.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
|
||||||
|
CAN_frame ZOE_NVROL_2_18DADBF1 = {.FD = false,
|
||||||
|
.ext_ID = true,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x18DADBF1,
|
||||||
|
.data = {0x04, 0x31, 0x01, 0xB0, 0x09, 0x00, 0xAA, 0xAA}};
|
||||||
|
//Enable temporisation before sleep
|
||||||
|
CAN_frame ZOE_SLEEP_1_18DADBF1 = {.FD = false,
|
||||||
|
.ext_ID = true,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x18DADBF1,
|
||||||
|
.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
|
||||||
|
CAN_frame ZOE_SLEEP_2_18DADBF1 = {.FD = false,
|
||||||
|
.ext_ID = true,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x18DADBF1,
|
||||||
|
.data = {0x04, 0x2E, 0x92, 0x81, 0x01, 0xAA, 0xAA, 0xAA}};
|
||||||
|
|
||||||
|
const uint16_t poll_commands[41] = {POLL_SOC,
|
||||||
|
POLL_USABLE_SOC,
|
||||||
|
POLL_SOH,
|
||||||
|
POLL_PACK_VOLTAGE,
|
||||||
|
POLL_MAX_CELL_VOLTAGE,
|
||||||
|
POLL_MIN_CELL_VOLTAGE,
|
||||||
|
POLL_12V,
|
||||||
|
POLL_AVG_TEMP,
|
||||||
|
POLL_MIN_TEMP,
|
||||||
|
POLL_MAX_TEMP,
|
||||||
|
POLL_MAX_POWER,
|
||||||
|
POLL_INTERLOCK,
|
||||||
|
POLL_KWH,
|
||||||
|
POLL_CURRENT,
|
||||||
|
POLL_CURRENT_OFFSET,
|
||||||
|
POLL_MAX_GENERATED,
|
||||||
|
POLL_MAX_AVAILABLE,
|
||||||
|
POLL_CURRENT_VOLTAGE,
|
||||||
|
POLL_CHARGING_STATUS,
|
||||||
|
POLL_REMAINING_CHARGE,
|
||||||
|
POLL_BALANCE_CAPACITY_TOTAL,
|
||||||
|
POLL_BALANCE_TIME_TOTAL,
|
||||||
|
POLL_BALANCE_CAPACITY_SLEEP,
|
||||||
|
POLL_BALANCE_TIME_SLEEP,
|
||||||
|
POLL_BALANCE_CAPACITY_WAKE,
|
||||||
|
POLL_BALANCE_TIME_WAKE,
|
||||||
|
POLL_BMS_STATE,
|
||||||
|
POLL_BALANCE_SWITCHES,
|
||||||
|
POLL_ENERGY_COMPLETE,
|
||||||
|
POLL_ENERGY_PARTIAL,
|
||||||
|
POLL_SLAVE_FAILURES,
|
||||||
|
POLL_MILEAGE,
|
||||||
|
POLL_FAN_SPEED,
|
||||||
|
POLL_FAN_PERIOD,
|
||||||
|
POLL_FAN_CONTROL,
|
||||||
|
POLL_FAN_DUTY,
|
||||||
|
POLL_TEMPORISATION,
|
||||||
|
POLL_TIME,
|
||||||
|
POLL_PACK_TIME,
|
||||||
|
POLL_SOC_MIN,
|
||||||
|
POLL_SOC_MAX};
|
||||||
|
static uint8_t poll_index = 0;
|
||||||
|
static uint16_t currentpoll = POLL_SOC;
|
||||||
|
static uint16_t reply_poll = 0;
|
||||||
|
|
||||||
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
|
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
|
||||||
|
|
||||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||||
datalayer.battery.status.soh_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
|
datalayer.battery.status.soh_pptt = battery_soh;
|
||||||
|
|
||||||
datalayer.battery.status.real_soc = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
|
if (battery_soc >= 300) {
|
||||||
|
datalayer.battery.status.real_soc = battery_soc - 300;
|
||||||
|
} else {
|
||||||
|
datalayer.battery.status.real_soc = 0;
|
||||||
|
}
|
||||||
|
|
||||||
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
|
datalayer.battery.status.voltage_dV = battery_pack_voltage;
|
||||||
|
|
||||||
datalayer.battery.status.current_dA = LB_Current;
|
datalayer.battery.status.current_dA = ((battery_current - 32640) * 0.3125);
|
||||||
|
|
||||||
//Calculate the remaining Wh amount from SOC% and max Wh value.
|
//Calculate the remaining Wh amount from SOC% and max Wh value.
|
||||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W;
|
datalayer.battery.status.max_discharge_power_W = battery_max_available * 10;
|
||||||
|
|
||||||
datalayer.battery.status.max_charge_power_W;
|
datalayer.battery.status.max_charge_power_W = battery_max_generated * 10;
|
||||||
|
|
||||||
datalayer.battery.status.active_power_W;
|
datalayer.battery.status.active_power_W =
|
||||||
|
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC;
|
datalayer.battery.status.temperature_min_dC = ((battery_min_temp - 640) * 0.625);
|
||||||
|
|
||||||
datalayer.battery.status.temperature_max_dC;
|
datalayer.battery.status.temperature_max_dC = ((battery_max_temp - 640) * 0.625);
|
||||||
|
|
||||||
datalayer.battery.status.cell_min_voltage_mV;
|
datalayer.battery.status.cell_min_voltage_mV = (battery_min_cell_voltage * 0.976563);
|
||||||
|
|
||||||
datalayer.battery.status.cell_max_voltage_mV;
|
datalayer.battery.status.cell_max_voltage_mV = (battery_max_cell_voltage * 0.976563);
|
||||||
|
|
||||||
|
// Update webserver datalayer
|
||||||
|
datalayer_extended.zoePH2.battery_soc = battery_soc;
|
||||||
|
datalayer_extended.zoePH2.battery_usable_soc = battery_usable_soc;
|
||||||
|
datalayer_extended.zoePH2.battery_soh = battery_soh;
|
||||||
|
datalayer_extended.zoePH2.battery_pack_voltage = battery_pack_voltage;
|
||||||
|
datalayer_extended.zoePH2.battery_max_cell_voltage = battery_max_cell_voltage;
|
||||||
|
datalayer_extended.zoePH2.battery_min_cell_voltage = battery_min_cell_voltage;
|
||||||
|
datalayer_extended.zoePH2.battery_12v = battery_12v;
|
||||||
|
datalayer_extended.zoePH2.battery_avg_temp = battery_avg_temp;
|
||||||
|
datalayer_extended.zoePH2.battery_min_temp = battery_min_temp;
|
||||||
|
datalayer_extended.zoePH2.battery_max_temp = battery_max_temp;
|
||||||
|
datalayer_extended.zoePH2.battery_max_power = battery_max_power;
|
||||||
|
datalayer_extended.zoePH2.battery_interlock = battery_interlock;
|
||||||
|
datalayer_extended.zoePH2.battery_kwh = battery_kwh;
|
||||||
|
datalayer_extended.zoePH2.battery_current = battery_current;
|
||||||
|
datalayer_extended.zoePH2.battery_current_offset = battery_current_offset;
|
||||||
|
datalayer_extended.zoePH2.battery_max_generated = battery_max_generated;
|
||||||
|
datalayer_extended.zoePH2.battery_max_available = battery_max_available;
|
||||||
|
datalayer_extended.zoePH2.battery_current_voltage = battery_current_voltage;
|
||||||
|
datalayer_extended.zoePH2.battery_charging_status = battery_charging_status;
|
||||||
|
datalayer_extended.zoePH2.battery_remaining_charge = battery_remaining_charge;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_capacity_total = battery_balance_capacity_total;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_time_total = battery_balance_time_total;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_capacity_sleep = battery_balance_capacity_sleep;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_time_sleep = battery_balance_time_sleep;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_capacity_wake = battery_balance_capacity_wake;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_time_wake = battery_balance_time_wake;
|
||||||
|
datalayer_extended.zoePH2.battery_bms_state = battery_bms_state;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_switches = battery_balance_switches;
|
||||||
|
datalayer_extended.zoePH2.battery_energy_complete = battery_energy_complete;
|
||||||
|
datalayer_extended.zoePH2.battery_energy_partial = battery_energy_partial;
|
||||||
|
datalayer_extended.zoePH2.battery_slave_failures = battery_slave_failures;
|
||||||
|
datalayer_extended.zoePH2.battery_mileage = battery_mileage;
|
||||||
|
datalayer_extended.zoePH2.battery_fan_speed = battery_fan_speed;
|
||||||
|
datalayer_extended.zoePH2.battery_fan_period = battery_fan_period;
|
||||||
|
datalayer_extended.zoePH2.battery_fan_control = battery_fan_control;
|
||||||
|
datalayer_extended.zoePH2.battery_fan_duty = battery_fan_duty;
|
||||||
|
datalayer_extended.zoePH2.battery_temporisation = battery_temporisation;
|
||||||
|
datalayer_extended.zoePH2.battery_time = battery_time;
|
||||||
|
datalayer_extended.zoePH2.battery_pack_time = battery_pack_time;
|
||||||
|
datalayer_extended.zoePH2.battery_soc_min = battery_soc_min;
|
||||||
|
datalayer_extended.zoePH2.battery_soc_max = battery_soc_max;
|
||||||
|
|
||||||
#ifdef DEBUG_VIA_USB
|
#ifdef DEBUG_VIA_USB
|
||||||
|
|
||||||
|
@ -67,7 +228,137 @@ void update_values_battery() { //This function maps all the values fetched via
|
||||||
void receive_can_battery(CAN_frame rx_frame) {
|
void receive_can_battery(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x18daf1db: // LBC Reply from active polling
|
case 0x18DAF1DB: // LBC Reply from active polling
|
||||||
|
//frame 2 & 3 contains
|
||||||
|
reply_poll = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
|
||||||
|
|
||||||
|
switch (reply_poll) {
|
||||||
|
case POLL_SOC:
|
||||||
|
battery_soc = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_USABLE_SOC:
|
||||||
|
battery_usable_soc = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_SOH:
|
||||||
|
battery_soh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_PACK_VOLTAGE:
|
||||||
|
battery_pack_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MAX_CELL_VOLTAGE:
|
||||||
|
battery_max_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MIN_CELL_VOLTAGE:
|
||||||
|
battery_min_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_12V:
|
||||||
|
battery_12v = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_AVG_TEMP:
|
||||||
|
battery_avg_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MIN_TEMP:
|
||||||
|
battery_min_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MAX_TEMP:
|
||||||
|
battery_max_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MAX_POWER:
|
||||||
|
battery_max_power = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_INTERLOCK:
|
||||||
|
battery_interlock = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_KWH:
|
||||||
|
battery_kwh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_CURRENT:
|
||||||
|
battery_current = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_CURRENT_OFFSET:
|
||||||
|
battery_current_offset = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MAX_GENERATED:
|
||||||
|
battery_max_generated = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MAX_AVAILABLE:
|
||||||
|
battery_max_available = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_CURRENT_VOLTAGE:
|
||||||
|
battery_current_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_CHARGING_STATUS:
|
||||||
|
battery_charging_status = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_REMAINING_CHARGE:
|
||||||
|
battery_remaining_charge = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_CAPACITY_TOTAL:
|
||||||
|
battery_balance_capacity_total = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_TIME_TOTAL:
|
||||||
|
battery_balance_time_total = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_CAPACITY_SLEEP:
|
||||||
|
battery_balance_capacity_sleep = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_TIME_SLEEP:
|
||||||
|
battery_balance_time_sleep = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_CAPACITY_WAKE:
|
||||||
|
battery_balance_capacity_wake = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_TIME_WAKE:
|
||||||
|
battery_balance_time_wake = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BMS_STATE:
|
||||||
|
battery_bms_state = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_SWITCHES:
|
||||||
|
battery_balance_switches = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_ENERGY_COMPLETE:
|
||||||
|
battery_energy_complete = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_ENERGY_PARTIAL:
|
||||||
|
battery_energy_partial = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_SLAVE_FAILURES:
|
||||||
|
battery_slave_failures = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MILEAGE:
|
||||||
|
battery_mileage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_FAN_SPEED:
|
||||||
|
battery_fan_speed = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_FAN_PERIOD:
|
||||||
|
battery_fan_period = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_FAN_CONTROL:
|
||||||
|
battery_fan_control = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_FAN_DUTY:
|
||||||
|
battery_fan_duty = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_TEMPORISATION:
|
||||||
|
battery_temporisation = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_TIME:
|
||||||
|
battery_time = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_PACK_TIME:
|
||||||
|
battery_pack_time = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_SOC_MIN:
|
||||||
|
battery_soc_min = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_SOC_MAX:
|
||||||
|
battery_soc_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
default: // Unknown reply
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -83,8 +374,16 @@ void send_can_battery() {
|
||||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200));
|
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200));
|
||||||
}
|
}
|
||||||
previousMillis200 = currentMillis;
|
previousMillis200 = currentMillis;
|
||||||
transmit_can(&ZOE_373, can_config.battery);
|
|
||||||
|
// Update current poll from the array
|
||||||
|
currentpoll = poll_commands[poll_index];
|
||||||
|
poll_index = (poll_index + 1) % 41;
|
||||||
|
|
||||||
|
ZOE_POLL_18DADBF1.data.u8[2] = (uint8_t)((currentpoll & 0xFF00) >> 8);
|
||||||
|
ZOE_POLL_18DADBF1.data.u8[3] = (uint8_t)(currentpoll & 0x00FF);
|
||||||
|
|
||||||
transmit_can(&ZOE_POLL_18DADBF1, can_config.battery);
|
transmit_can(&ZOE_POLL_18DADBF1, can_config.battery);
|
||||||
|
transmit_can(&ZOE_373, can_config.battery);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,4 +12,46 @@
|
||||||
void setup_battery(void);
|
void setup_battery(void);
|
||||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||||
|
|
||||||
|
#define POLL_SOC 0x9001
|
||||||
|
#define POLL_USABLE_SOC 0x9002
|
||||||
|
#define POLL_SOH 0x9003
|
||||||
|
#define POLL_PACK_VOLTAGE 0x9005
|
||||||
|
#define POLL_MAX_CELL_VOLTAGE 0x9007
|
||||||
|
#define POLL_MIN_CELL_VOLTAGE 0x9009
|
||||||
|
#define POLL_12V 0x9011
|
||||||
|
#define POLL_AVG_TEMP 0x9012
|
||||||
|
#define POLL_MIN_TEMP 0x9013
|
||||||
|
#define POLL_MAX_TEMP 0x9014
|
||||||
|
#define POLL_MAX_POWER 0x9018
|
||||||
|
#define POLL_INTERLOCK 0x901A
|
||||||
|
#define POLL_KWH 0x91C8
|
||||||
|
#define POLL_CURRENT 0x925D
|
||||||
|
#define POLL_CURRENT_OFFSET 0x900C
|
||||||
|
#define POLL_MAX_GENERATED 0x900E
|
||||||
|
#define POLL_MAX_AVAILABLE 0x900F
|
||||||
|
#define POLL_CURRENT_VOLTAGE 0x9130
|
||||||
|
#define POLL_CHARGING_STATUS 0x9019
|
||||||
|
#define POLL_REMAINING_CHARGE 0xF45B
|
||||||
|
#define POLL_BALANCE_CAPACITY_TOTAL 0x924F
|
||||||
|
#define POLL_BALANCE_TIME_TOTAL 0x9250
|
||||||
|
#define POLL_BALANCE_CAPACITY_SLEEP 0x9251
|
||||||
|
#define POLL_BALANCE_TIME_SLEEP 0x9252
|
||||||
|
#define POLL_BALANCE_CAPACITY_WAKE 0x9262
|
||||||
|
#define POLL_BALANCE_TIME_WAKE 0x9263
|
||||||
|
#define POLL_BMS_STATE 0x9259
|
||||||
|
#define POLL_BALANCE_SWITCHES 0x912B
|
||||||
|
#define POLL_ENERGY_COMPLETE 0x9210
|
||||||
|
#define POLL_ENERGY_PARTIAL 0x9215
|
||||||
|
#define POLL_SLAVE_FAILURES 0x9129
|
||||||
|
#define POLL_MILEAGE 0x91CF
|
||||||
|
#define POLL_FAN_SPEED 0x912E
|
||||||
|
#define POLL_FAN_PERIOD 0x91F4
|
||||||
|
#define POLL_FAN_CONTROL 0x91C9
|
||||||
|
#define POLL_FAN_DUTY 0x91F5
|
||||||
|
#define POLL_TEMPORISATION 0x9281
|
||||||
|
#define POLL_TIME 0x9261
|
||||||
|
#define POLL_PACK_TIME 0x91C1
|
||||||
|
#define POLL_SOC_MIN 0x91B9
|
||||||
|
#define POLL_SOC_MAX 0x91BA
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -22,6 +22,8 @@ void print_units(char* header, int value, char* units) {
|
||||||
|
|
||||||
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
|
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
|
||||||
|
|
||||||
|
datalayer.battery.info.number_of_cells = 96;
|
||||||
|
|
||||||
datalayer.battery.status.real_soc = 5000; // 50.00%
|
datalayer.battery.status.real_soc = 5000; // 50.00%
|
||||||
|
|
||||||
datalayer.battery.status.soh_pptt = 9900; // 99.00%
|
datalayer.battery.status.soh_pptt = 9900; // 99.00%
|
||||||
|
@ -49,7 +51,7 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
||||||
datalayer.battery.status.max_charge_power_W = 5000; // 5kW
|
datalayer.battery.status.max_charge_power_W = 5000; // 5kW
|
||||||
|
|
||||||
for (int i = 0; i < 97; ++i) {
|
for (int i = 0; i < 97; ++i) {
|
||||||
datalayer.battery.status.cell_voltages_mV[i] = 3500 + i;
|
datalayer.battery.status.cell_voltages_mV[i] = 3700 + random(-20, 21);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Fake that we get CAN messages
|
//Fake that we get CAN messages
|
||||||
|
@ -71,6 +73,78 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
|
||||||
|
void update_values_battery2() { // Handle the values coming in from battery #2
|
||||||
|
|
||||||
|
datalayer.battery2.info.number_of_cells = 96;
|
||||||
|
|
||||||
|
datalayer.battery2.status.real_soc = 5000; // 50.00%
|
||||||
|
|
||||||
|
datalayer.battery2.status.soh_pptt = 9900; // 99.00%
|
||||||
|
|
||||||
|
//datalayer.battery.status.voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
|
||||||
|
|
||||||
|
datalayer.battery2.status.current_dA = 0; // 0 A
|
||||||
|
|
||||||
|
datalayer.battery2.info.total_capacity_Wh = 30000; // 30kWh
|
||||||
|
|
||||||
|
datalayer.battery2.status.remaining_capacity_Wh = 15000; // 15kWh
|
||||||
|
|
||||||
|
datalayer.battery2.status.cell_max_voltage_mV = 3596;
|
||||||
|
|
||||||
|
datalayer.battery2.status.cell_min_voltage_mV = 3500;
|
||||||
|
|
||||||
|
datalayer.battery2.status.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
|
||||||
|
|
||||||
|
datalayer.battery2.status.max_discharge_power_W = 5000; // 5kW
|
||||||
|
|
||||||
|
datalayer.battery2.status.max_charge_power_W = 5000; // 5kW
|
||||||
|
|
||||||
|
for (int i = 0; i < 97; ++i) {
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[i] = 3700 + random(-20, 21);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fake that we get CAN messages
|
||||||
|
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
|
||||||
|
/*Finally print out values to serial if configured to do so*/
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("FAKE Values battery 2 going to inverter");
|
||||||
|
print_units("SOH 2 %: ", (datalayer.battery2.status.soh_pptt * 0.01), "% ");
|
||||||
|
print_units(", SOC 2 %: ", (datalayer.battery2.status.reported_soc * 0.01), "% ");
|
||||||
|
print_units(", Voltage 2: ", (datalayer.battery2.status.voltage_dV * 0.1), "V ");
|
||||||
|
print_units(", Max discharge power 2: ", datalayer.battery2.status.max_discharge_power_W, "W ");
|
||||||
|
print_units(", Max charge power 2: ", datalayer.battery2.status.max_charge_power_W, "W ");
|
||||||
|
print_units(", Max temp 2: ", (datalayer.battery2.status.temperature_max_dC * 0.1), "°C ");
|
||||||
|
print_units(", Min temp 2: ", (datalayer.battery2.status.temperature_min_dC * 0.1), "°C ");
|
||||||
|
print_units(", Max cell voltage 2: ", datalayer.battery2.status.cell_max_voltage_mV, "mV ");
|
||||||
|
print_units(", Min cell voltage 2: ", datalayer.battery2.status.cell_min_voltage_mV, "mV ");
|
||||||
|
Serial.println("");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void receive_can_battery2(CAN_frame rx_frame) {
|
||||||
|
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
// 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) {
|
void receive_can_battery(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
// All CAN messages recieved will be logged via serial
|
// All CAN messages recieved will be logged via serial
|
||||||
|
@ -97,6 +171,8 @@ void send_can_battery() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup_battery(void) { // Performs one time setup at startup
|
void setup_battery(void) { // Performs one time setup at startup
|
||||||
|
randomSeed(analogRead(0));
|
||||||
|
|
||||||
#ifdef DEBUG_VIA_USB
|
#ifdef DEBUG_VIA_USB
|
||||||
Serial.println("Test mode with fake battery selected");
|
Serial.println("Test mode with fake battery selected");
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -216,6 +216,52 @@ typedef struct {
|
||||||
|
|
||||||
} DATALAYER_INFO_NISSAN_LEAF;
|
} DATALAYER_INFO_NISSAN_LEAF;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/** uint16_t */
|
||||||
|
/** Values WIP*/
|
||||||
|
uint16_t battery_soc = 0;
|
||||||
|
uint16_t battery_usable_soc = 0;
|
||||||
|
uint16_t battery_soh = 0;
|
||||||
|
uint16_t battery_pack_voltage = 0;
|
||||||
|
uint16_t battery_max_cell_voltage = 0;
|
||||||
|
uint16_t battery_min_cell_voltage = 0;
|
||||||
|
uint16_t battery_12v = 0;
|
||||||
|
uint16_t battery_avg_temp = 0;
|
||||||
|
uint16_t battery_min_temp = 0;
|
||||||
|
uint16_t battery_max_temp = 0;
|
||||||
|
uint16_t battery_max_power = 0;
|
||||||
|
uint16_t battery_interlock = 0;
|
||||||
|
uint16_t battery_kwh = 0;
|
||||||
|
uint16_t battery_current = 0;
|
||||||
|
uint16_t battery_current_offset = 0;
|
||||||
|
uint16_t battery_max_generated = 0;
|
||||||
|
uint16_t battery_max_available = 0;
|
||||||
|
uint16_t battery_current_voltage = 0;
|
||||||
|
uint16_t battery_charging_status = 0;
|
||||||
|
uint16_t battery_remaining_charge = 0;
|
||||||
|
uint16_t battery_balance_capacity_total = 0;
|
||||||
|
uint16_t battery_balance_time_total = 0;
|
||||||
|
uint16_t battery_balance_capacity_sleep = 0;
|
||||||
|
uint16_t battery_balance_time_sleep = 0;
|
||||||
|
uint16_t battery_balance_capacity_wake = 0;
|
||||||
|
uint16_t battery_balance_time_wake = 0;
|
||||||
|
uint16_t battery_bms_state = 0;
|
||||||
|
uint16_t battery_balance_switches = 0;
|
||||||
|
uint16_t battery_energy_complete = 0;
|
||||||
|
uint16_t battery_energy_partial = 0;
|
||||||
|
uint16_t battery_slave_failures = 0;
|
||||||
|
uint16_t battery_mileage = 0;
|
||||||
|
uint16_t battery_fan_speed = 0;
|
||||||
|
uint16_t battery_fan_period = 0;
|
||||||
|
uint16_t battery_fan_control = 0;
|
||||||
|
uint16_t battery_fan_duty = 0;
|
||||||
|
uint16_t battery_temporisation = 0;
|
||||||
|
uint16_t battery_time = 0;
|
||||||
|
uint16_t battery_pack_time = 0;
|
||||||
|
uint16_t battery_soc_min = 0;
|
||||||
|
uint16_t battery_soc_max = 0;
|
||||||
|
} DATALAYER_INFO_ZOE_PH2;
|
||||||
|
|
||||||
class DataLayerExtended {
|
class DataLayerExtended {
|
||||||
public:
|
public:
|
||||||
DATALAYER_INFO_BMWI3 bmwi3;
|
DATALAYER_INFO_BMWI3 bmwi3;
|
||||||
|
@ -223,6 +269,7 @@ class DataLayerExtended {
|
||||||
DATALAYER_INFO_CELLPOWER cellpower;
|
DATALAYER_INFO_CELLPOWER cellpower;
|
||||||
DATALAYER_INFO_TESLA tesla;
|
DATALAYER_INFO_TESLA tesla;
|
||||||
DATALAYER_INFO_NISSAN_LEAF nissanleaf;
|
DATALAYER_INFO_NISSAN_LEAF nissanleaf;
|
||||||
|
DATALAYER_INFO_ZOE_PH2 zoePH2;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern DataLayerExtended datalayer_extended;
|
extern DataLayerExtended datalayer_extended;
|
||||||
|
|
|
@ -66,15 +66,35 @@ SensorConfig sensorConfigs[] = {
|
||||||
{"max_charge_power", "Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", "power"},
|
{"max_charge_power", "Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", "power"},
|
||||||
{"bms_status", "BMS Status", "{{ value_json.bms_status }}", "", ""},
|
{"bms_status", "BMS Status", "{{ value_json.bms_status }}", "", ""},
|
||||||
{"pause_status", "Pause Status", "{{ value_json.pause_status }}", "", ""},
|
{"pause_status", "Pause Status", "{{ value_json.pause_status }}", "", ""},
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
{"SOC_2", "SOC 2 (scaled)", "{{ value_json.SOC_2 }}", "%", "battery"},
|
||||||
|
{"SOC_real_2", "SOC 2 (real)", "{{ value_json.SOC_real_2 }}", "%", "battery"},
|
||||||
|
{"state_of_health_2", "State Of Health 2", "{{ value_json.state_of_health_2 }}", "%", "battery"},
|
||||||
|
{"temperature_min_2", "Temperature Min 2", "{{ value_json.temperature_min_2 }}", "°C", "temperature"},
|
||||||
|
{"temperature_max_2", "Temperature Max 2", "{{ value_json.temperature_max_2 }}", "°C", "temperature"},
|
||||||
|
{"stat_batt_power_2", "Stat Batt Power 2", "{{ value_json.stat_batt_power_2 }}", "W", "power"},
|
||||||
|
{"battery_current_2", "Battery 2 Current", "{{ value_json.battery_current_2 }}", "A", "current"},
|
||||||
|
{"cell_max_voltage_2", "Cell Max Voltage 2", "{{ value_json.cell_max_voltage_2 }}", "V", "voltage"},
|
||||||
|
{"cell_min_voltage_2", "Cell Min Voltage 2", "{{ value_json.cell_min_voltage_2 }}", "V", "voltage"},
|
||||||
|
{"battery_voltage_2", "Battery 2 Voltage", "{{ value_json.battery_voltage_2 }}", "V", "voltage"},
|
||||||
|
{"total_capacity_2", "Battery 2 Total Capacity", "{{ value_json.total_capacity_2 }}", "Wh", "energy"},
|
||||||
|
{"remaining_capacity_2", "Battery 2 Remaining Capacity (scaled)", "{{ value_json.remaining_capacity_2 }}", "Wh",
|
||||||
|
"energy"},
|
||||||
|
{"remaining_capacity_real_2", "Battery 2 Remaining Capacity (real)", "{{ value_json.remaining_capacity_real_2 }}",
|
||||||
|
"Wh", "energy"},
|
||||||
|
{"max_discharge_power_2", "Battery 2 Max Discharge Power", "{{ value_json.max_discharge_power_2 }}", "W", "power"},
|
||||||
|
{"max_charge_power_2", "Battery 2 Max Charge Power", "{{ value_json.max_charge_power_2 }}", "W", "power"},
|
||||||
|
{"bms_status_2", "BMS 2 Status", "{{ value_json.bms_status_2 }}", "", ""},
|
||||||
|
{"pause_status_2", "Pause Status 2", "{{ value_json.pause_status_2 }}", "", ""},
|
||||||
|
#endif // DOUBLE_BATTERY
|
||||||
};
|
};
|
||||||
|
|
||||||
static String generateCommonInfoAutoConfigTopic(const char* object_id) {
|
static String generateCommonInfoAutoConfigTopic(const char* object_id) {
|
||||||
return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config";
|
return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config";
|
||||||
}
|
}
|
||||||
|
|
||||||
static String generateCellVoltageAutoConfigTopic(int cell_number) {
|
static String generateCellVoltageAutoConfigTopic(int cell_number, String battery_suffix) {
|
||||||
return "homeassistant/sensor/" + topic_name + "/cell_voltage" + String(cell_number) + "/config";
|
return "homeassistant/sensor/" + topic_name + "/cell_voltage" + battery_suffix + String(cell_number) + "/config";
|
||||||
}
|
}
|
||||||
|
|
||||||
static String generateEventsAutoConfigTopic(const char* object_id) {
|
static String generateEventsAutoConfigTopic(const char* object_id) {
|
||||||
|
@ -127,7 +147,7 @@ static void publish_common_info(void) {
|
||||||
doc["pause_status"] = get_emulator_pause_status();
|
doc["pause_status"] = get_emulator_pause_status();
|
||||||
|
|
||||||
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
|
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
|
||||||
if (datalayer.battery.status.bms_status == ACTIVE && allowed_to_send_CAN && millis() > BOOTUP_TIME) {
|
if (datalayer.battery.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) {
|
||||||
doc["SOC"] = ((float)datalayer.battery.status.reported_soc) / 100.0;
|
doc["SOC"] = ((float)datalayer.battery.status.reported_soc) / 100.0;
|
||||||
doc["SOC_real"] = ((float)datalayer.battery.status.real_soc) / 100.0;
|
doc["SOC_real"] = ((float)datalayer.battery.status.real_soc) / 100.0;
|
||||||
doc["state_of_health"] = ((float)datalayer.battery.status.soh_pptt) / 100.0;
|
doc["state_of_health"] = ((float)datalayer.battery.status.soh_pptt) / 100.0;
|
||||||
|
@ -148,7 +168,30 @@ static void publish_common_info(void) {
|
||||||
doc["max_discharge_power"] = ((float)datalayer.battery.status.max_discharge_power_W);
|
doc["max_discharge_power"] = ((float)datalayer.battery.status.max_discharge_power_W);
|
||||||
doc["max_charge_power"] = ((float)datalayer.battery.status.max_charge_power_W);
|
doc["max_charge_power"] = ((float)datalayer.battery.status.max_charge_power_W);
|
||||||
}
|
}
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
//only publish these values if BMS is active and we are comunication with the battery (can send CAN messages to the battery)
|
||||||
|
if (datalayer.battery2.status.CAN_battery_still_alive && allowed_to_send_CAN && millis() > BOOTUP_TIME) {
|
||||||
|
doc["SOC_2"] = ((float)datalayer.battery2.status.reported_soc) / 100.0;
|
||||||
|
doc["SOC_real_2"] = ((float)datalayer.battery2.status.real_soc) / 100.0;
|
||||||
|
doc["state_of_health_2"] = ((float)datalayer.battery2.status.soh_pptt) / 100.0;
|
||||||
|
doc["temperature_min_2"] = ((float)((int16_t)datalayer.battery2.status.temperature_min_dC)) / 10.0;
|
||||||
|
doc["temperature_max_2"] = ((float)((int16_t)datalayer.battery2.status.temperature_max_dC)) / 10.0;
|
||||||
|
doc["stat_batt_power_2"] = ((float)((int32_t)datalayer.battery2.status.active_power_W));
|
||||||
|
doc["battery_current_2"] = ((float)((int16_t)datalayer.battery2.status.current_dA)) / 10.0;
|
||||||
|
doc["battery_voltage_2"] = ((float)datalayer.battery2.status.voltage_dV) / 10.0;
|
||||||
|
// publish only if cell voltages have been populated...
|
||||||
|
if (datalayer.battery2.info.number_of_cells != 0u &&
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) {
|
||||||
|
doc["cell_max_voltage_2"] = ((float)datalayer.battery2.status.cell_max_voltage_mV) / 1000.0;
|
||||||
|
doc["cell_min_voltage_2"] = ((float)datalayer.battery2.status.cell_min_voltage_mV) / 1000.0;
|
||||||
|
}
|
||||||
|
doc["total_capacity_2"] = ((float)datalayer.battery2.info.total_capacity_Wh);
|
||||||
|
doc["remaining_capacity_real_2"] = ((float)datalayer.battery2.status.remaining_capacity_Wh);
|
||||||
|
doc["remaining_capacity_2"] = ((float)datalayer.battery2.status.reported_remaining_capacity_Wh);
|
||||||
|
doc["max_discharge_power_2"] = ((float)datalayer.battery2.status.max_discharge_power_W);
|
||||||
|
doc["max_charge_power_2"] = ((float)datalayer.battery2.status.max_charge_power_W);
|
||||||
|
}
|
||||||
|
#endif // DOUBLE_BATTERY
|
||||||
serializeJson(doc, mqtt_msg);
|
serializeJson(doc, mqtt_msg);
|
||||||
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
|
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
|
||||||
#ifdef DEBUG_VIA_USB
|
#ifdef DEBUG_VIA_USB
|
||||||
|
@ -167,47 +210,79 @@ static void publish_cell_voltages(void) {
|
||||||
#endif // HA_AUTODISCOVERY
|
#endif // HA_AUTODISCOVERY
|
||||||
static JsonDocument doc;
|
static JsonDocument doc;
|
||||||
static String state_topic = topic_name + "/spec_data";
|
static String state_topic = topic_name + "/spec_data";
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
static String state_topic_2 = topic_name + "/spec_data_2";
|
||||||
|
|
||||||
|
#endif // DOUBLE_BATTERY
|
||||||
|
|
||||||
// If the cell voltage number isn't initialized...
|
|
||||||
if (datalayer.battery.info.number_of_cells == 0u) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#ifdef HA_AUTODISCOVERY
|
#ifdef HA_AUTODISCOVERY
|
||||||
if (mqtt_first_transmission == true) {
|
if (mqtt_first_transmission == true) {
|
||||||
mqtt_first_transmission = false;
|
mqtt_first_transmission = false;
|
||||||
String topic = "homeassistant/sensor/battery-emulator/cell_voltage";
|
|
||||||
|
|
||||||
for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
|
// If the cell voltage number isn't initialized...
|
||||||
int cellNumber = i + 1;
|
if (datalayer.battery.info.number_of_cells != 0u) {
|
||||||
doc["name"] = "Battery Cell Voltage " + String(cellNumber);
|
|
||||||
doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber);
|
|
||||||
doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber);
|
|
||||||
doc["device_class"] = "voltage";
|
|
||||||
doc["state_class"] = "measurement";
|
|
||||||
doc["state_topic"] = state_topic;
|
|
||||||
doc["unit_of_measurement"] = "V";
|
|
||||||
doc["enabled_by_default"] = true;
|
|
||||||
doc["expire_after"] = 240;
|
|
||||||
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
|
|
||||||
doc["device"]["identifiers"][0] = "battery-emulator";
|
|
||||||
doc["device"]["manufacturer"] = "DalaTech";
|
|
||||||
doc["device"]["model"] = "BatteryEmulator";
|
|
||||||
doc["device"]["name"] = device_name;
|
|
||||||
doc["origin"]["name"] = "BatteryEmulator";
|
|
||||||
doc["origin"]["sw"] = String(version_number) + "-mqtt";
|
|
||||||
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
|
|
||||||
|
|
||||||
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
|
for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
|
||||||
mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber).c_str(), mqtt_msg, true);
|
int cellNumber = i + 1;
|
||||||
|
doc["name"] = "Battery Cell Voltage " + String(cellNumber);
|
||||||
|
doc["object_id"] = object_id_prefix + "battery_voltage_cell" + String(cellNumber);
|
||||||
|
doc["unique_id"] = topic_name + "_battery_voltage_cell" + String(cellNumber);
|
||||||
|
doc["device_class"] = "voltage";
|
||||||
|
doc["state_class"] = "measurement";
|
||||||
|
doc["state_topic"] = state_topic;
|
||||||
|
doc["unit_of_measurement"] = "V";
|
||||||
|
doc["enabled_by_default"] = true;
|
||||||
|
doc["expire_after"] = 240;
|
||||||
|
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
|
||||||
|
doc["device"]["identifiers"][0] = "battery-emulator";
|
||||||
|
doc["device"]["manufacturer"] = "DalaTech";
|
||||||
|
doc["device"]["model"] = "BatteryEmulator";
|
||||||
|
doc["device"]["name"] = device_name;
|
||||||
|
doc["origin"]["name"] = "BatteryEmulator";
|
||||||
|
doc["origin"]["sw"] = String(version_number) + "-mqtt";
|
||||||
|
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
|
||||||
|
|
||||||
|
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
|
||||||
|
mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "").c_str(), mqtt_msg, true);
|
||||||
|
}
|
||||||
|
doc.clear(); // clear after sending autoconfig
|
||||||
}
|
}
|
||||||
doc.clear(); // clear after sending autoconfig
|
#ifdef DOUBLE_BATTERY
|
||||||
} else {
|
// If the cell voltage number isn't initialized...
|
||||||
|
if (datalayer.battery2.info.number_of_cells != 0u) {
|
||||||
|
|
||||||
|
for (int i = 0; i < datalayer.battery.info.number_of_cells; i++) {
|
||||||
|
int cellNumber = i + 1;
|
||||||
|
doc["name"] = "Battery 2 Cell Voltage " + String(cellNumber);
|
||||||
|
doc["object_id"] = object_id_prefix + "battery_2_voltage_cell" + String(cellNumber);
|
||||||
|
doc["unique_id"] = topic_name + "_battery_2_voltage_cell" + String(cellNumber);
|
||||||
|
doc["device_class"] = "voltage";
|
||||||
|
doc["state_class"] = "measurement";
|
||||||
|
doc["state_topic"] = state_topic_2;
|
||||||
|
doc["unit_of_measurement"] = "V";
|
||||||
|
doc["enabled_by_default"] = true;
|
||||||
|
doc["expire_after"] = 240;
|
||||||
|
doc["value_template"] = "{{ value_json.cell_voltages[" + String(i) + "] }}";
|
||||||
|
doc["device"]["identifiers"][0] = "battery-emulator";
|
||||||
|
doc["device"]["manufacturer"] = "DalaTech";
|
||||||
|
doc["device"]["model"] = "BatteryEmulator";
|
||||||
|
doc["device"]["name"] = device_name;
|
||||||
|
doc["origin"]["name"] = "BatteryEmulator";
|
||||||
|
doc["origin"]["sw"] = String(version_number) + "-mqtt";
|
||||||
|
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
|
||||||
|
|
||||||
|
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
|
||||||
|
mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, "_2_").c_str(), mqtt_msg, true);
|
||||||
|
}
|
||||||
|
doc.clear(); // clear after sending autoconfig
|
||||||
|
}
|
||||||
|
#endif // DOUBLE_BATTERY
|
||||||
|
}
|
||||||
#endif // HA_AUTODISCOVERY
|
#endif // HA_AUTODISCOVERY
|
||||||
// If cell voltages haven't been populated...
|
|
||||||
if (datalayer.battery.info.number_of_cells == 0u ||
|
// If cell voltages have been populated...
|
||||||
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] == 0u) {
|
if (datalayer.battery.info.number_of_cells != 0u &&
|
||||||
return;
|
datalayer.battery.status.cell_voltages_mV[datalayer.battery.info.number_of_cells - 1] != 0u) {
|
||||||
}
|
|
||||||
|
|
||||||
JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>();
|
JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>();
|
||||||
for (size_t i = 0; i < datalayer.battery.info.number_of_cells; ++i) {
|
for (size_t i = 0; i < datalayer.battery.info.number_of_cells; ++i) {
|
||||||
|
@ -222,9 +297,28 @@ static void publish_cell_voltages(void) {
|
||||||
#endif // DEBUG_VIA_USB
|
#endif // DEBUG_VIA_USB
|
||||||
}
|
}
|
||||||
doc.clear();
|
doc.clear();
|
||||||
#ifdef HA_AUTODISCOVERY
|
|
||||||
}
|
}
|
||||||
#endif // HA_AUTODISCOVERY
|
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
// If cell voltages have been populated...
|
||||||
|
if (datalayer.battery2.info.number_of_cells != 0u &&
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[datalayer.battery2.info.number_of_cells - 1] != 0u) {
|
||||||
|
|
||||||
|
JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>();
|
||||||
|
for (size_t i = 0; i < datalayer.battery2.info.number_of_cells; ++i) {
|
||||||
|
cell_voltages.add(((float)datalayer.battery2.status.cell_voltages_mV[i]) / 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
|
||||||
|
|
||||||
|
if (!mqtt_publish(state_topic_2.c_str(), mqtt_msg, false)) {
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("Cell voltage MQTT msg could not be sent");
|
||||||
|
#endif // DEBUG_VIA_USB
|
||||||
|
}
|
||||||
|
doc.clear();
|
||||||
|
}
|
||||||
|
#endif // DOUBLE_BATTERY
|
||||||
}
|
}
|
||||||
|
|
||||||
void publish_events() {
|
void publish_events() {
|
||||||
|
|
|
@ -27,14 +27,14 @@ void update_machineryprotection() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Battery is overheated!
|
// Battery is overheated!
|
||||||
if (datalayer.battery.status.temperature_max_dC > 500) {
|
if (datalayer.battery.status.temperature_max_dC > BATTERY_MAXTEMPERATURE) {
|
||||||
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
|
set_event(EVENT_BATTERY_OVERHEAT, datalayer.battery.status.temperature_max_dC);
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_BATTERY_OVERHEAT);
|
clear_event(EVENT_BATTERY_OVERHEAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Battery is frozen!
|
// Battery is frozen!
|
||||||
if (datalayer.battery.status.temperature_min_dC < -250) {
|
if (datalayer.battery.status.temperature_min_dC < BATTERY_MINTEMPERATURE) {
|
||||||
set_event(EVENT_BATTERY_FROZEN, datalayer.battery.status.temperature_min_dC);
|
set_event(EVENT_BATTERY_FROZEN, datalayer.battery.status.temperature_min_dC);
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_BATTERY_FROZEN);
|
clear_event(EVENT_BATTERY_FROZEN);
|
||||||
|
|
|
@ -150,6 +150,7 @@ void init_events(void) {
|
||||||
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
|
||||||
|
events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||||
|
@ -281,6 +282,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
||||||
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
|
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
|
||||||
case EVENT_CAN_INVERTER_MISSING:
|
case EVENT_CAN_INVERTER_MISSING:
|
||||||
return "Warning: Inverter not sending messages on CAN bus. Check wiring!";
|
return "Warning: Inverter not sending messages on CAN bus. Check wiring!";
|
||||||
|
case EVENT_CONTACTOR_WELDED:
|
||||||
|
return "Warning: Contactors sticking/welded. Inspect battery with caution!";
|
||||||
case EVENT_CHARGE_LIMIT_EXCEEDED:
|
case EVENT_CHARGE_LIMIT_EXCEEDED:
|
||||||
return "Info: Inverter is charging faster than battery is allowing.";
|
return "Info: Inverter is charging faster than battery is allowing.";
|
||||||
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
|
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
||||||
|
|
||||||
#define EE_MAGIC_HEADER_VALUE 0x0016 // 0x0000 to 0xFFFF
|
#define EE_MAGIC_HEADER_VALUE 0x0017 // 0x0000 to 0xFFFF
|
||||||
|
|
||||||
#define GENERATE_ENUM(ENUM) ENUM,
|
#define GENERATE_ENUM(ENUM) ENUM,
|
||||||
#define GENERATE_STRING(STRING) #STRING,
|
#define GENERATE_STRING(STRING) #STRING,
|
||||||
|
@ -37,6 +37,7 @@
|
||||||
XX(EVENT_CAN_TX_FAILURE) \
|
XX(EVENT_CAN_TX_FAILURE) \
|
||||||
XX(EVENT_CAN_INVERTER_MISSING) \
|
XX(EVENT_CAN_INVERTER_MISSING) \
|
||||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||||
|
XX(EVENT_CONTACTOR_WELDED) \
|
||||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||||
XX(EVENT_WATER_INGRESS) \
|
XX(EVENT_WATER_INGRESS) \
|
||||||
XX(EVENT_12V_LOW) \
|
XX(EVENT_12V_LOW) \
|
||||||
|
|
|
@ -299,8 +299,55 @@ String advanced_battery_processor(const String& var) {
|
||||||
content += "<h4>Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "</h4>";
|
content += "<h4>Challenge failed: " + String(datalayer_extended.nissanleaf.challengeFailed) + "</h4>";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||||
|
content += "<h4>soc: " + String(datalayer_extended.zoePH2.battery_soc) + "</h4>";
|
||||||
|
content += "<h4>usable soc: " + String(datalayer_extended.zoePH2.battery_usable_soc) + "</h4>";
|
||||||
|
content += "<h4>soh: " + String(datalayer_extended.zoePH2.battery_soh) + "</h4>";
|
||||||
|
content += "<h4>pack voltage: " + String(datalayer_extended.zoePH2.battery_pack_voltage) + "</h4>";
|
||||||
|
content += "<h4>max cell voltage: " + String(datalayer_extended.zoePH2.battery_max_cell_voltage) + "</h4>";
|
||||||
|
content += "<h4>min cell voltage: " + String(datalayer_extended.zoePH2.battery_min_cell_voltage) + "</h4>";
|
||||||
|
content += "<h4>12v: " + String(datalayer_extended.zoePH2.battery_12v) + "</h4>";
|
||||||
|
content += "<h4>avg temp: " + String(datalayer_extended.zoePH2.battery_avg_temp) + "</h4>";
|
||||||
|
content += "<h4>min temp: " + String(datalayer_extended.zoePH2.battery_min_temp) + "</h4>";
|
||||||
|
content += "<h4>max temp: " + String(datalayer_extended.zoePH2.battery_max_temp) + "</h4>";
|
||||||
|
content += "<h4>max power: " + String(datalayer_extended.zoePH2.battery_max_power) + "</h4>";
|
||||||
|
content += "<h4>interlock: " + String(datalayer_extended.zoePH2.battery_interlock) + "</h4>";
|
||||||
|
content += "<h4>kwh: " + String(datalayer_extended.zoePH2.battery_kwh) + "</h4>";
|
||||||
|
content += "<h4>current: " + String(datalayer_extended.zoePH2.battery_current) + "</h4>";
|
||||||
|
content += "<h4>current offset: " + String(datalayer_extended.zoePH2.battery_current_offset) + "</h4>";
|
||||||
|
content += "<h4>max generated: " + String(datalayer_extended.zoePH2.battery_max_generated) + "</h4>";
|
||||||
|
content += "<h4>max available: " + String(datalayer_extended.zoePH2.battery_max_available) + "</h4>";
|
||||||
|
content += "<h4>current voltage: " + String(datalayer_extended.zoePH2.battery_current_voltage) + "</h4>";
|
||||||
|
content += "<h4>charging status: " + String(datalayer_extended.zoePH2.battery_charging_status) + "</h4>";
|
||||||
|
content += "<h4>remaining charge: " + String(datalayer_extended.zoePH2.battery_remaining_charge) + "</h4>";
|
||||||
|
content +=
|
||||||
|
"<h4>balance capacity total: " + String(datalayer_extended.zoePH2.battery_balance_capacity_total) + "</h4>";
|
||||||
|
content += "<h4>balance time total: " + String(datalayer_extended.zoePH2.battery_balance_time_total) + "</h4>";
|
||||||
|
content +=
|
||||||
|
"<h4>balance capacity sleep: " + String(datalayer_extended.zoePH2.battery_balance_capacity_sleep) + "</h4>";
|
||||||
|
content += "<h4>balance time sleep: " + String(datalayer_extended.zoePH2.battery_balance_time_sleep) + "</h4>";
|
||||||
|
content +=
|
||||||
|
"<h4>balance capacity wake: " + String(datalayer_extended.zoePH2.battery_balance_capacity_wake) + "</h4>";
|
||||||
|
content += "<h4>balance time wake: " + String(datalayer_extended.zoePH2.battery_balance_time_wake) + "</h4>";
|
||||||
|
content += "<h4>bms state: " + String(datalayer_extended.zoePH2.battery_bms_state) + "</h4>";
|
||||||
|
content += "<h4>balance switches: " + String(datalayer_extended.zoePH2.battery_balance_switches) + "</h4>";
|
||||||
|
content += "<h4>energy complete: " + String(datalayer_extended.zoePH2.battery_energy_complete) + "</h4>";
|
||||||
|
content += "<h4>energy partial: " + String(datalayer_extended.zoePH2.battery_energy_partial) + "</h4>";
|
||||||
|
content += "<h4>slave failures: " + String(datalayer_extended.zoePH2.battery_slave_failures) + "</h4>";
|
||||||
|
content += "<h4>mileage: " + String(datalayer_extended.zoePH2.battery_mileage) + "</h4>";
|
||||||
|
content += "<h4>fan speed: " + String(datalayer_extended.zoePH2.battery_fan_speed) + "</h4>";
|
||||||
|
content += "<h4>fan period: " + String(datalayer_extended.zoePH2.battery_fan_period) + "</h4>";
|
||||||
|
content += "<h4>fan control: " + String(datalayer_extended.zoePH2.battery_fan_control) + "</h4>";
|
||||||
|
content += "<h4>fan duty: " + String(datalayer_extended.zoePH2.battery_fan_duty) + "</h4>";
|
||||||
|
content += "<h4>temporisation: " + String(datalayer_extended.zoePH2.battery_temporisation) + "</h4>";
|
||||||
|
content += "<h4>time: " + String(datalayer_extended.zoePH2.battery_time) + "</h4>";
|
||||||
|
content += "<h4>pack time: " + String(datalayer_extended.zoePH2.battery_pack_time) + "</h4>";
|
||||||
|
content += "<h4>soc min: " + String(datalayer_extended.zoePH2.battery_soc_min) + "</h4>";
|
||||||
|
content += "<h4>soc max: " + String(datalayer_extended.zoePH2.battery_soc_max) + "</h4>";
|
||||||
|
#endif //RENAULT_ZOE_GEN2_BATTERY
|
||||||
|
|
||||||
#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \
|
#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && !defined(BMW_I3_BATTERY) && \
|
||||||
!defined(BYD_ATTO_3_BATTERY) && !defined(CELLPOWER_BMS) //Only the listed types have extra info
|
!defined(BYD_ATTO_3_BATTERY) && !defined(RENAULT_ZOE_GEN2_BATTERY) && !defined(CELLPOWER_BMS)
|
||||||
content += "No extra information available for this battery type";
|
content += "No extra information available for this battery type";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -505,6 +505,9 @@ String processor(const String& var) {
|
||||||
#ifdef PYLON_CAN
|
#ifdef PYLON_CAN
|
||||||
content += "Pylontech battery over CAN bus";
|
content += "Pylontech battery over CAN bus";
|
||||||
#endif // PYLON_CAN
|
#endif // PYLON_CAN
|
||||||
|
#ifdef PYLON_LV_CAN
|
||||||
|
content += "Pylontech LV battery over CAN bus";
|
||||||
|
#endif // PYLON_LV_CAN
|
||||||
#ifdef SERIAL_LINK_TRANSMITTER
|
#ifdef SERIAL_LINK_TRANSMITTER
|
||||||
content += "Serial link to another LilyGo board";
|
content += "Serial link to another LilyGo board";
|
||||||
#endif // SERIAL_LINK_TRANSMITTER
|
#endif // SERIAL_LINK_TRANSMITTER
|
||||||
|
@ -729,6 +732,27 @@ String processor(const String& var) {
|
||||||
content += "<span style='color: red;'>OFF</span>";
|
content += "<span style='color: red;'>OFF</span>";
|
||||||
}
|
}
|
||||||
content += "</h4>";
|
content += "</h4>";
|
||||||
|
|
||||||
|
content += "<h4>Pre Charge: ";
|
||||||
|
if (digitalRead(PRECHARGE_PIN) == HIGH) {
|
||||||
|
content += "<span style='color: green;'>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
content += " Cont. Neg.: ";
|
||||||
|
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
|
||||||
|
content += "<span style='color: green;'>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
content += " Cont. Pos.: ";
|
||||||
|
if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) {
|
||||||
|
content += "<span style='color: green;'>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
content += "</h4>";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Close the block
|
// Close the block
|
||||||
|
@ -771,7 +795,9 @@ String processor(const String& var) {
|
||||||
content += "<h4 style='color: white;'>Current: " + String(currentFloat, 1) + " A</h4>";
|
content += "<h4 style='color: white;'>Current: " + String(currentFloat, 1) + " A</h4>";
|
||||||
content += formatPowerValue("Power", powerFloat, "", 1);
|
content += formatPowerValue("Power", powerFloat, "", 1);
|
||||||
content += formatPowerValue("Total capacity", datalayer.battery2.info.total_capacity_Wh, "h", 0);
|
content += formatPowerValue("Total capacity", datalayer.battery2.info.total_capacity_Wh, "h", 0);
|
||||||
content += formatPowerValue("Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
|
content += formatPowerValue("Real Remaining capacity", datalayer.battery2.status.remaining_capacity_Wh, "h", 1);
|
||||||
|
content +=
|
||||||
|
formatPowerValue("Scaled Remaining capacity", datalayer.battery2.status.reported_remaining_capacity_Wh, "h", 1);
|
||||||
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1);
|
content += formatPowerValue("Max discharge power", datalayer.battery2.status.max_discharge_power_W, "", 1);
|
||||||
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1);
|
content += formatPowerValue("Max charge power", datalayer.battery2.status.max_charge_power_W, "", 1);
|
||||||
content += "<h4>Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
|
content += "<h4>Cell max: " + String(datalayer.battery2.status.cell_max_voltage_mV) + " mV</h4>";
|
||||||
|
@ -813,6 +839,11 @@ String processor(const String& var) {
|
||||||
content += "<span style='color: red;'>✕</span></h4>";
|
content += "<span style='color: red;'>✕</span></h4>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (emulator_pause_status == NORMAL)
|
||||||
|
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||||
|
else
|
||||||
|
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||||
|
|
||||||
#ifdef CONTACTOR_CONTROL
|
#ifdef CONTACTOR_CONTROL
|
||||||
content += "<h4>Contactors controlled by Battery-Emulator: ";
|
content += "<h4>Contactors controlled by Battery-Emulator: ";
|
||||||
if (datalayer.system.status.contactor_control_closed) {
|
if (datalayer.system.status.contactor_control_closed) {
|
||||||
|
@ -821,12 +852,28 @@ String processor(const String& var) {
|
||||||
content += "<span style='color: red;'>OFF</span>";
|
content += "<span style='color: red;'>OFF</span>";
|
||||||
}
|
}
|
||||||
content += "</h4>";
|
content += "</h4>";
|
||||||
#endif
|
|
||||||
|
|
||||||
if (emulator_pause_status == NORMAL)
|
content += "<h4>Pre Charge: ";
|
||||||
content += "<h4>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
if (digitalRead(PRECHARGE_PIN) == HIGH) {
|
||||||
else
|
content += "<span style='color: green;'>✓</span>";
|
||||||
content += "<h4 style='color: red;'>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
content += " Cont. Neg.: ";
|
||||||
|
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
|
||||||
|
content += "<span style='color: green;'>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
content += " Cont. Pos.: ";
|
||||||
|
if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) {
|
||||||
|
content += "<span style='color: green;'>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
content += "</h4>";
|
||||||
|
#endif
|
||||||
|
|
||||||
content += "</div>";
|
content += "</div>";
|
||||||
content += "</div>";
|
content += "</div>";
|
||||||
|
|
|
@ -27,6 +27,10 @@
|
||||||
#include "PYLON-CAN.h"
|
#include "PYLON-CAN.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PYLON_LV_CAN
|
||||||
|
#include "PYLON-LV-CAN.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef SMA_CAN
|
#ifdef SMA_CAN
|
||||||
#include "SMA-CAN.h"
|
#include "SMA-CAN.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
142
Software/src/inverter/PYLON-LV-CAN.cpp
Normal file
142
Software/src/inverter/PYLON-LV-CAN.cpp
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
#include "../include.h"
|
||||||
|
#ifdef PYLON_LV_CAN
|
||||||
|
#include "../datalayer/datalayer.h"
|
||||||
|
#include "PYLON-LV-CAN.h"
|
||||||
|
|
||||||
|
/* Do not change code below unless you are sure what you are doing */
|
||||||
|
|
||||||
|
static unsigned long previousMillis1000ms = 0;
|
||||||
|
|
||||||
|
CAN_frame PYLON_351 = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 6,
|
||||||
|
.ID = 0x351,
|
||||||
|
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
|
CAN_frame PYLON_355 = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x355, .data = {0x00, 0x00, 0x00, 0x00}};
|
||||||
|
CAN_frame PYLON_356 = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 6,
|
||||||
|
.ID = 0x356,
|
||||||
|
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
|
CAN_frame PYLON_359 = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 7,
|
||||||
|
.ID = 0x359,
|
||||||
|
.data = {0x00, 0x00, 0x00, 0x00, PACK_NUMBER, 'P', 'N'}};
|
||||||
|
CAN_frame PYLON_35C = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x35C, .data = {0x00, 0x00}};
|
||||||
|
CAN_frame PYLON_35E = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x35E,
|
||||||
|
.data = {
|
||||||
|
MANUFACTURER_NAME[0],
|
||||||
|
MANUFACTURER_NAME[1],
|
||||||
|
MANUFACTURER_NAME[2],
|
||||||
|
MANUFACTURER_NAME[3],
|
||||||
|
MANUFACTURER_NAME[4],
|
||||||
|
MANUFACTURER_NAME[5],
|
||||||
|
MANUFACTURER_NAME[6],
|
||||||
|
MANUFACTURER_NAME[7],
|
||||||
|
}};
|
||||||
|
|
||||||
|
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_355.data.u8[0] = (datalayer.battery.status.reported_soc / 10) & 0xff;
|
||||||
|
PYLON_355.data.u8[1] = (datalayer.battery.status.reported_soc / 10) >> 8;
|
||||||
|
PYLON_355.data.u8[2] = (datalayer.battery.status.soh_pptt / 10) & 0xff;
|
||||||
|
PYLON_355.data.u8[3] = (datalayer.battery.status.soh_pptt / 10) >> 8;
|
||||||
|
|
||||||
|
PYLON_356.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff;
|
||||||
|
PYLON_356.data.u8[1] = datalayer.battery.status.voltage_dV >> 8;
|
||||||
|
PYLON_356.data.u8[2] = datalayer.battery.status.current_dA & 0xff;
|
||||||
|
PYLON_356.data.u8[3] = datalayer.battery.status.current_dA >> 8;
|
||||||
|
PYLON_356.data.u8[4] = datalayer.battery.status.temperature_max_dC & 0xff;
|
||||||
|
PYLON_356.data.u8[5] = datalayer.battery.status.temperature_max_dC >> 8;
|
||||||
|
|
||||||
|
// initialize all errors and warnings to 0
|
||||||
|
PYLON_359.data.u8[0] = 0x00;
|
||||||
|
PYLON_359.data.u8[1] = 0x00;
|
||||||
|
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';
|
||||||
|
|
||||||
|
// ERRORS
|
||||||
|
if (datalayer.battery.status.current_dA >= maxDischargeCurrent)
|
||||||
|
PYLON_359.data.u8[0] |= 0x80;
|
||||||
|
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE)
|
||||||
|
PYLON_359.data.u8[0] |= 0x10;
|
||||||
|
if (datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE)
|
||||||
|
PYLON_359.data.u8[0] |= 0x0C;
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
PYLON_359.data.u8[2] |= 0x80;
|
||||||
|
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE * WARNINGS_PERCENT / 100)
|
||||||
|
PYLON_359.data.u8[2] |= 0x10;
|
||||||
|
if (datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE * WARNINGS_PERCENT / 100)
|
||||||
|
PYLON_359.data.u8[2] |= 0x0C;
|
||||||
|
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)
|
||||||
|
PYLON_359.data.u8[3] |= 0x01;
|
||||||
|
|
||||||
|
PYLON_35C.data.u8[0] = 0xC0; // enable charging and discharging
|
||||||
|
PYLON_35C.data.u8[1] = 0x00;
|
||||||
|
if (datalayer.battery.status.real_soc <= datalayer.battery.settings.min_percentage)
|
||||||
|
PYLON_35C.data.u8[0] = 0xA0; // enable charing, set charge immediately
|
||||||
|
if (datalayer.battery.status.real_soc >= datalayer.battery.settings.max_percentage)
|
||||||
|
PYLON_35C.data.u8[0] = 0x40; // enable discharging only
|
||||||
|
|
||||||
|
// PYLON_35E is pre-filled with the manufacturer name
|
||||||
|
}
|
||||||
|
|
||||||
|
void receive_can_inverter(CAN_frame rx_frame) {
|
||||||
|
switch (rx_frame.ID) {
|
||||||
|
case 0x305: //Message originating from inverter.
|
||||||
|
// according to the spec, this message includes only 0-bytes
|
||||||
|
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_can_inverter() {
|
||||||
|
unsigned long currentMillis = millis();
|
||||||
|
|
||||||
|
if (currentMillis - previousMillis1000ms >= 1000) {
|
||||||
|
previousMillis1000ms = currentMillis;
|
||||||
|
|
||||||
|
transmit_can(&PYLON_351, can_config.inverter);
|
||||||
|
transmit_can(&PYLON_355, can_config.inverter);
|
||||||
|
transmit_can(&PYLON_356, can_config.inverter);
|
||||||
|
transmit_can(&PYLON_359, can_config.inverter);
|
||||||
|
transmit_can(&PYLON_35C, can_config.inverter);
|
||||||
|
transmit_can(&PYLON_35E, can_config.inverter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
16
Software/src/inverter/PYLON-LV-CAN.h
Normal file
16
Software/src/inverter/PYLON-LV-CAN.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#ifndef PYLON_LV_CAN_H
|
||||||
|
#define PYLON_LV_CAN_H
|
||||||
|
#include "../include.h"
|
||||||
|
|
||||||
|
#define CAN_INVERTER_SELECTED
|
||||||
|
|
||||||
|
#define MANUFACTURER_NAME "BatEmuLV"
|
||||||
|
#define PACK_NUMBER 0x01
|
||||||
|
// 80 means after reaching 80% of a nominal value a warning is produced (e.g. 80% of max current)
|
||||||
|
#define WARNINGS_PERCENT 80
|
||||||
|
|
||||||
|
void send_system_data();
|
||||||
|
void send_setup_info();
|
||||||
|
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Add table
Add a link
Reference in a new issue