Merge branch 'main' into feature/bmw-ix-support

This commit is contained in:
wjcloudy 2024-11-08 10:48:46 +00:00
commit a52d6a3233
31 changed files with 1745 additions and 433 deletions

View file

@ -35,6 +35,7 @@ jobs:
battery: battery:
- BMW_I3_BATTERY - BMW_I3_BATTERY
- BYD_ATTO_3_BATTERY - BYD_ATTO_3_BATTERY
- CELLPOWER_BMS
- CHADEMO_BATTERY - CHADEMO_BATTERY
- IMIEV_CZERO_ION_BATTERY - IMIEV_CZERO_ION_BATTERY
- JAGUAR_IPACE_BATTERY - JAGUAR_IPACE_BATTERY

View file

@ -53,10 +53,10 @@
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_5_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
unsigned long previousMillis10ms = 50; unsigned long previousMillis10ms = 50;
unsigned long previousMillisUpdateVal = 0; unsigned long previousMillisUpdateVal = 0;
@ -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;
@ -283,9 +286,8 @@ void core_loop(void* task_time_us) {
} }
END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us); END_TIME_MEASUREMENT_MAX(time_10ms, datalayer.system.status.time_10ms_us);
START_TIME_MEASUREMENT(time_5s); START_TIME_MEASUREMENT(time_values);
if (millis() - previousMillisUpdateVal >= intervalUpdateValues) // Every 5s normally if (millis() - previousMillisUpdateVal >= intervalUpdateValues) {
{
previousMillisUpdateVal = millis(); // Order matters on the update_loop! previousMillisUpdateVal = millis(); // Order matters on the update_loop!
update_values_battery(); // Fetch battery values update_values_battery(); // Fetch battery values
#ifdef DOUBLE_BATTERY #ifdef DOUBLE_BATTERY
@ -300,7 +302,7 @@ void core_loop(void* task_time_us) {
set_event(EVENT_DUMMY_ERROR, (uint8_t)millis()); set_event(EVENT_DUMMY_ERROR, (uint8_t)millis());
} }
} }
END_TIME_MEASUREMENT_MAX(time_5s, datalayer.system.status.time_5s_us); END_TIME_MEASUREMENT_MAX(time_values, datalayer.system.status.time_values_us);
START_TIME_MEASUREMENT(cantx); START_TIME_MEASUREMENT(cantx);
// Output // Output
@ -316,7 +318,7 @@ void core_loop(void* task_time_us) {
// Record snapshots of task times // Record snapshots of task times
datalayer.system.status.time_snap_comm_us = datalayer.system.status.time_comm_us; datalayer.system.status.time_snap_comm_us = datalayer.system.status.time_comm_us;
datalayer.system.status.time_snap_10ms_us = datalayer.system.status.time_10ms_us; datalayer.system.status.time_snap_10ms_us = datalayer.system.status.time_10ms_us;
datalayer.system.status.time_snap_5s_us = datalayer.system.status.time_5s_us; datalayer.system.status.time_snap_values_us = datalayer.system.status.time_values_us;
datalayer.system.status.time_snap_cantx_us = datalayer.system.status.time_cantx_us; datalayer.system.status.time_snap_cantx_us = datalayer.system.status.time_cantx_us;
datalayer.system.status.time_snap_ota_us = datalayer.system.status.time_ota_us; datalayer.system.status.time_snap_ota_us = datalayer.system.status.time_ota_us;
} }
@ -327,7 +329,7 @@ void core_loop(void* task_time_us) {
datalayer.system.status.time_ota_us = 0; datalayer.system.status.time_ota_us = 0;
datalayer.system.status.time_comm_us = 0; datalayer.system.status.time_comm_us = 0;
datalayer.system.status.time_10ms_us = 0; datalayer.system.status.time_10ms_us = 0;
datalayer.system.status.time_5s_us = 0; datalayer.system.status.time_values_us = 0;
datalayer.system.status.time_cantx_us = 0; datalayer.system.status.time_cantx_us = 0;
datalayer.system.status.core_task_10s_max_us = 0; datalayer.system.status.core_task_10s_max_us = 0;
} }
@ -454,7 +456,7 @@ void init_CAN() {
Serial.println("CAN FD add-on (ESP32+MCP2517) selected"); Serial.println("CAN FD add-on (ESP32+MCP2517) selected");
#endif #endif
SPI.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI); SPI.begin(MCP2517_SCK, MCP2517_SDO, MCP2517_SDI);
ACAN2517FDSettings settings(ACAN2517FDSettings::OSC_40MHz, 500 * 1000, ACAN2517FDSettings settings(CAN_FD_CRYSTAL_FREQUENCY_MHZ, 500 * 1000,
DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s DataBitRateFactor::x4); // Arbitration bit rate: 500 kbit/s, data bit rate: 2 Mbit/s
#ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN #ifdef USE_CANFD_INTERFACE_AS_CLASSIC_CAN
settings.mRequestedMode = ACAN2517FDSettings::Normal20B; // ListenOnly / Normal20B / NormalFD settings.mRequestedMode = ACAN2517FDSettings::Normal20B; // ListenOnly / Normal20B / NormalFD
@ -742,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;
@ -857,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,19 +871,38 @@ void update_scaled_values() {
datalayer.battery.status.reported_soc = calc_soc; datalayer.battery.status.reported_soc = calc_soc;
// Calculate the scaled remaining capacity in Wh // Calculate the scaled remaining capacity in Wh
if (datalayer.battery.info.total_capacity_Wh > 0) { if (datalayer.battery.info.total_capacity_Wh > 0 && datalayer.battery.status.real_soc > 0) {
uint32_t calc_capacity; calc_max_capacity = (datalayer.battery.status.remaining_capacity_Wh * 10000 / datalayer.battery.status.real_soc);
// remove % capacity not used in min_percentage to total_capacity_Wh calc_reserved_capacity = calc_max_capacity * datalayer.battery.settings.min_percentage / 10000;
calc_capacity = datalayer.battery.settings.min_percentage * datalayer.battery.info.total_capacity_Wh / 10000; // remove % capacity reserved in min_percentage to total_capacity_Wh
calc_capacity = datalayer.battery.status.remaining_capacity_Wh - calc_capacity; datalayer.battery.status.reported_remaining_capacity_Wh =
datalayer.battery.status.reported_remaining_capacity_Wh = calc_capacity; datalayer.battery.status.remaining_capacity_Wh - calc_reserved_capacity;
} else { } else {
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
@ -930,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
} }

View file

@ -45,8 +45,16 @@ const char* http_password = "admin"; // password to webserver authentication;
#ifdef MQTT #ifdef MQTT
const char* mqtt_user = "REDACTED"; // Set NULL for no username const char* mqtt_user = "REDACTED"; // Set NULL for no username
const char* mqtt_password = "REDACTED"; // Set NULL for no password const char* mqtt_password = "REDACTED"; // Set NULL for no password
#endif // USE_MQTT #ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME
#endif // WIFI const char* mqtt_topic_name =
"BE"; // Custom MQTT topic name. Previously, the name was automatically set to "battery-emulator_esp32-XXXXXX"
const char* mqtt_object_id_prefix =
"be_"; // Custom prefix for MQTT object ID. Previously, the prefix was automatically set to "esp32-XXXXXX_"
const char* mqtt_device_name =
"Battery Emulator"; // Custom device name in Home Assistant. Previously, the name was automatically set to "BatteryEmulator_esp32-XXXXXX"
#endif // MQTT_MANUAL_TOPIC_OBJECT_NAME
#endif // USE_MQTT
#endif // WIFI
#ifdef EQUIPMENT_STOP_BUTTON #ifdef EQUIPMENT_STOP_BUTTON
// Equipment stop button behavior. Use NC button for safety reasons. // Equipment stop button behavior. Use NC button for safety reasons.

View file

@ -12,6 +12,7 @@
//#define BMW_I3_BATTERY //#define BMW_I3_BATTERY
//#define BMW_IX_BATTERY //#define BMW_IX_BATTERY
//#define BYD_ATTO_3_BATTERY //#define BYD_ATTO_3_BATTERY
//#define CELLPOWER_BMS
//#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below //#define CHADEMO_BATTERY //NOTE: inherently enables CONTACTOR_CONTROL below
//#define IMIEV_CZERO_ION_BATTERY //#define IMIEV_CZERO_ION_BATTERY
//#define JAGUAR_IPACE_BATTERY //#define JAGUAR_IPACE_BATTERY
@ -39,7 +40,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
@ -58,6 +60,11 @@
//#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery) //#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 chip (Needed for some inverters / double battery)
#define CRYSTAL_FREQUENCY_MHZ 8 //DUAL_CAN option, what is your MCP2515 add-on boards crystal frequency? #define CRYSTAL_FREQUENCY_MHZ 8 //DUAL_CAN option, what is your MCP2515 add-on boards crystal frequency?
//#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board //#define CAN_FD //Enable this line to activate an isolated secondary CAN-FD bus using add-on MCP2518FD chip / Native CANFD on Stark board
#ifdef CAN_FD // CAN_FD additional options if enabled
#define CAN_FD_CRYSTAL_FREQUENCY_MHZ \
ACAN2517FDSettings:: \
OSC_40MHz //CAN_FD option, what is your MCP2518 add-on boards crystal frequency? (Default OSC_40MHz)
#endif
//#define USE_CANFD_INTERFACE_AS_CLASSIC_CAN // Enable this line if you intend to use the CANFD as normal CAN //#define USE_CANFD_INTERFACE_AS_CLASSIC_CAN // Enable this line if you intend to use the CANFD as normal CAN
//#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter) //#define SERIAL_LINK_RECEIVER //Enable this line to receive battery data over RS485 pins from another Lilygo (This LilyGo interfaces with inverter)
//#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery) //#define SERIAL_LINK_TRANSMITTER //Enable this line to send battery data over RS485 pins to another Lilygo (This LilyGo interfaces with battery)
@ -76,6 +83,13 @@
// #define MQTT // Enable this line to enable MQTT // #define MQTT // Enable this line to enable MQTT
#define MQTT_SERVER "192.168.xxx.yyy" #define MQTT_SERVER "192.168.xxx.yyy"
#define MQTT_PORT 1883 #define MQTT_PORT 1883
#define MQTT_MANUAL_TOPIC_OBJECT_NAME // Enable this to use custom MQTT topic, object ID prefix, and device name. \
// WARNING: If this is not defined, the previous default naming format \
// 'battery-emulator_esp32-XXXXXX' (based on hardware ID) will be used. \
// This naming convention was in place until version 7.5.0. \
// Users should check the version from which they are updating, as this change \
// may break compatibility with previous versions of MQTT naming. \
// Please refer to USER_SETTINGS.cpp for configuration options.
/* Home Assistant options */ /* Home Assistant options */
#define HA_AUTODISCOVERY // Enable this line to send Home Assistant autodiscovery messages. If not enabled manual configuration of Home Assitant is required #define HA_AUTODISCOVERY // Enable this line to send Home Assistant autodiscovery messages. If not enabled manual configuration of Home Assitant is required
@ -96,6 +110,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)

View file

@ -14,6 +14,10 @@
#include "BYD-ATTO-3-BATTERY.h" #include "BYD-ATTO-3-BATTERY.h"
#endif #endif
#ifdef CELLPOWER_BMS
#include "CELLPOWER-BMS.h"
#endif
#ifdef CHADEMO_BATTERY #ifdef CHADEMO_BATTERY
#include "CHADEMO-BATTERY.h" #include "CHADEMO-BATTERY.h"
#endif #endif

View file

@ -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

View file

@ -1,6 +1,7 @@
#include "../include.h" #include "../include.h"
#ifdef BYD_ATTO_3_BATTERY #ifdef BYD_ATTO_3_BATTERY
#include "../datalayer/datalayer.h" #include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h"
#include "../devboard/utils/events.h" #include "../devboard/utils/events.h"
#include "BYD-ATTO-3-BATTERY.h" #include "BYD-ATTO-3-BATTERY.h"
@ -18,13 +19,14 @@ static uint8_t counter_50ms = 0;
static uint8_t counter_100ms = 0; static uint8_t counter_100ms = 0;
static uint8_t frame6_counter = 0xB; static uint8_t frame6_counter = 0xB;
static uint8_t frame7_counter = 0x5; static uint8_t frame7_counter = 0x5;
static uint16_t battery_voltage = 0;
static int16_t battery_temperature_ambient = 0; static int16_t battery_temperature_ambient = 0;
static int16_t battery_daughterboard_temperatures[10]; static int16_t battery_daughterboard_temperatures[10];
static int16_t battery_lowest_temperature = 0; static int16_t battery_lowest_temperature = 0;
static int16_t battery_highest_temperature = 0; static int16_t battery_highest_temperature = 0;
static int16_t battery_calc_min_temperature = 0; static int16_t battery_calc_min_temperature = 0;
static int16_t battery_calc_max_temperature = 0; static int16_t battery_calc_max_temperature = 0;
static uint16_t battery_highprecision_SOC = 0;
static uint16_t BMS_SOC = 0; static uint16_t BMS_SOC = 0;
static uint16_t BMS_voltage = 0; static uint16_t BMS_voltage = 0;
static int16_t BMS_current = 0; static int16_t BMS_current = 0;
@ -40,6 +42,7 @@ static int16_t battery2_lowest_temperature = 0;
static int16_t battery2_highest_temperature = 0; static int16_t battery2_highest_temperature = 0;
static int16_t battery2_calc_min_temperature = 0; static int16_t battery2_calc_min_temperature = 0;
static int16_t battery2_calc_max_temperature = 0; static int16_t battery2_calc_max_temperature = 0;
static uint16_t battery2_highprecision_SOC = 0;
static uint16_t BMS2_SOC = 0; static uint16_t BMS2_SOC = 0;
static uint16_t BMS2_voltage = 0; static uint16_t BMS2_voltage = 0;
static int16_t BMS2_current = 0; static int16_t BMS2_current = 0;
@ -142,6 +145,14 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.temperature_min_dC = battery_calc_min_temperature * 10; // Add decimals datalayer.battery.status.temperature_min_dC = battery_calc_min_temperature * 10; // Add decimals
datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10; datalayer.battery.status.temperature_max_dC = battery_calc_max_temperature * 10;
// Update webserver datalayer
datalayer_extended.bydAtto3.SOC_estimated = datalayer.battery.status.real_soc;
//Once we implement switching logic, remember to change from where the estimated is taken
datalayer_extended.bydAtto3.SOC_highprec = battery_highprecision_SOC;
datalayer_extended.bydAtto3.SOC_polled = BMS_SOC;
datalayer_extended.bydAtto3.voltage_periodic = battery_voltage;
datalayer_extended.bydAtto3.voltage_polled = BMS_voltage;
} }
void receive_can_battery(CAN_frame rx_frame) { void receive_can_battery(CAN_frame rx_frame) {
@ -220,6 +231,8 @@ void receive_can_battery(CAN_frame rx_frame) {
case 0x444: //9E,01,88,13,64,64,98,65 case 0x444: //9E,01,88,13,64,64,98,65
//9A,01,B6,13,64,64,98,3B //407.5V 18deg //9A,01,B6,13,64,64,98,3B //407.5V 18deg
//9B,01,B8,13,64,64,98,38 //408.5V 14deg //9B,01,B8,13,64,64,98,38 //408.5V 14deg
battery_voltage = ((rx_frame.data.u8[1] & 0x0F) << 8) | rx_frame.data.u8[0];
//battery_temperature_something = rx_frame.data.u8[7] - 40; resides in frame 7
break; break;
case 0x445: //00,98,FF,FF,63,20,4E,98 - Static, values never changes between logs case 0x445: //00,98,FF,FF,63,20,4E,98 - Static, values never changes between logs
break; break;
@ -228,8 +241,9 @@ void receive_can_battery(CAN_frame rx_frame) {
case 0x447: // Seems to contain more temperatures, highest and lowest? case 0x447: // Seems to contain more temperatures, highest and lowest?
//06,38,01,3B,E0,03,39,69 //06,38,01,3B,E0,03,39,69
//06,36,02,36,E0,03,36,72, //06,36,02,36,E0,03,36,72,
battery_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now battery_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]; // 03 E0 = 992 = 99.2%
battery_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now battery_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now
battery_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now
break; break;
case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs
break; break;
@ -540,8 +554,9 @@ void receive_can_battery2(CAN_frame rx_frame) {
case 0x447: // Seems to contain more temperatures, highest and lowest? case 0x447: // Seems to contain more temperatures, highest and lowest?
//06,38,01,3B,E0,03,39,69 //06,38,01,3B,E0,03,39,69
//06,36,02,36,E0,03,36,72, //06,36,02,36,E0,03,36,72,
battery2_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now battery2_highprecision_SOC = ((rx_frame.data.u8[5] & 0x0F) << 8) | rx_frame.data.u8[4]; // 03 E0 = 992 = 99.2%
battery2_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now battery2_lowest_temperature = (rx_frame.data.u8[1] - 40); //Best guess for now
battery2_highest_temperature = (rx_frame.data.u8[3] - 40); //Best guess for now
break; break;
case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs case 0x47B: //01,FF,FF,FF,FF,FF,FF,FF - Static, values never changes between logs
break; break;

View file

@ -0,0 +1,349 @@
#include "../include.h"
#ifdef CELLPOWER_BMS
#include "../datalayer/datalayer.h"
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
#include "../devboard/utils/events.h"
#include "CELLPOWER-BMS.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was sent
//Actual content messages
// Optional add-on charger module. Might not be needed to send these towards the BMS to keep it happy.
CAN_frame CELLPOWER_18FF50E9 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E9,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E8 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E8,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E7 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E7,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
CAN_frame CELLPOWER_18FF50E5 = {.FD = false,
.ext_ID = true,
.DLC = 5,
.ID = 0x18FF50E5,
.data = {0x00, 0x00, 0x00, 0x00, 0x00}};
static bool system_state_discharge = false;
static bool system_state_charge = false;
static bool system_state_cellbalancing = false;
static bool system_state_tricklecharge = false;
static bool system_state_idle = false;
static bool system_state_chargecompleted = false;
static bool system_state_maintenancecharge = false;
static bool IO_state_main_positive_relay = false;
static bool IO_state_main_negative_relay = false;
static bool IO_state_charge_enable = false;
static bool IO_state_precharge_relay = false;
static bool IO_state_discharge_enable = false;
static bool IO_state_IO_6 = false;
static bool IO_state_IO_7 = false;
static bool IO_state_IO_8 = false;
static bool error_Cell_overvoltage = false;
static bool error_Cell_undervoltage = false;
static bool error_Cell_end_of_life_voltage = false;
static bool error_Cell_voltage_misread = false;
static bool error_Cell_over_temperature = false;
static bool error_Cell_under_temperature = false;
static bool error_Cell_unmanaged = false;
static bool error_LMU_over_temperature = false;
static bool error_LMU_under_temperature = false;
static bool error_Temp_sensor_open_circuit = false;
static bool error_Temp_sensor_short_circuit = false;
static bool error_SUB_communication = false;
static bool error_LMU_communication = false;
static bool error_Over_current_IN = false;
static bool error_Over_current_OUT = false;
static bool error_Short_circuit = false;
static bool error_Leak_detected = false;
static bool error_Leak_detection_failed = false;
static bool error_Voltage_difference = false;
static bool error_BMCU_supply_over_voltage = false;
static bool error_BMCU_supply_under_voltage = false;
static bool error_Main_positive_contactor = false;
static bool error_Main_negative_contactor = false;
static bool error_Precharge_contactor = false;
static bool error_Midpack_contactor = false;
static bool error_Precharge_timeout = false;
static bool error_Emergency_connector_override = false;
static bool warning_High_cell_voltage = false;
static bool warning_Low_cell_voltage = false;
static bool warning_High_cell_temperature = false;
static bool warning_Low_cell_temperature = false;
static bool warning_High_LMU_temperature = false;
static bool warning_Low_LMU_temperature = false;
static bool warning_SUB_communication_interfered = false;
static bool warning_LMU_communication_interfered = false;
static bool warning_High_current_IN = false;
static bool warning_High_current_OUT = false;
static bool warning_Pack_resistance_difference = false;
static bool warning_High_pack_resistance = false;
static bool warning_Cell_resistance_difference = false;
static bool warning_High_cell_resistance = false;
static bool warning_High_BMCU_supply_voltage = false;
static bool warning_Low_BMCU_supply_voltage = false;
static bool warning_Low_SOC = false;
static bool warning_Balancing_required_OCV_model = false;
static bool warning_Charger_not_responding = false;
static uint16_t cell_voltage_max_mV = 3700;
static uint16_t cell_voltage_min_mV = 3700;
static int8_t pack_temperature_high_C = 0;
static int8_t pack_temperature_low_C = 0;
static uint16_t battery_pack_voltage_dV = 3700;
static int16_t battery_pack_current_dA = 0;
static uint8_t battery_SOH_percentage = 99;
static uint8_t battery_SOC_percentage = 50;
static uint16_t battery_remaining_dAh = 0;
static uint8_t cell_with_highest_voltage = 0;
static uint8_t cell_with_lowest_voltage = 0;
static uint16_t requested_charge_current_dA = 0;
static uint16_t average_charge_current_dA = 0;
static uint16_t actual_charge_current_dA = 0;
static bool requested_exceeding_average_current = 0;
static bool error_state = false;
void update_values_battery() {
/* Update values from CAN */
datalayer.battery.status.real_soc = battery_SOC_percentage * 100;
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.soh_pptt = battery_SOH_percentage * 100;
datalayer.battery.status.voltage_dV = battery_pack_voltage_dV;
datalayer.battery.status.current_dA = battery_pack_current_dA;
datalayer.battery.status.active_power_W = //Power in watts, Negative = charging batt
((datalayer.battery.status.voltage_dV * datalayer.battery.status.current_dA) / 100);
datalayer.battery.status.max_charge_power_W = 5000; //TODO, is this available via CAN?
datalayer.battery.status.max_discharge_power_W = 5000; //TODO, is this available via CAN?
datalayer.battery.status.temperature_min_dC = (int16_t)(pack_temperature_low_C * 10);
datalayer.battery.status.temperature_max_dC = (int16_t)(pack_temperature_high_C * 10);
datalayer.battery.status.cell_max_voltage_mV = cell_voltage_max_mV;
datalayer.battery.status.cell_min_voltage_mV = cell_voltage_min_mV;
/* Update webserver datalayer */
datalayer_extended.cellpower.system_state_discharge = system_state_discharge;
datalayer_extended.cellpower.system_state_charge = system_state_charge;
datalayer_extended.cellpower.system_state_cellbalancing = system_state_cellbalancing;
datalayer_extended.cellpower.system_state_tricklecharge = system_state_tricklecharge;
datalayer_extended.cellpower.system_state_idle = system_state_idle;
datalayer_extended.cellpower.system_state_chargecompleted = system_state_chargecompleted;
datalayer_extended.cellpower.system_state_maintenancecharge = system_state_maintenancecharge;
datalayer_extended.cellpower.IO_state_main_positive_relay = IO_state_main_positive_relay;
datalayer_extended.cellpower.IO_state_main_negative_relay = IO_state_main_negative_relay;
datalayer_extended.cellpower.IO_state_charge_enable = IO_state_charge_enable;
datalayer_extended.cellpower.IO_state_precharge_relay = IO_state_precharge_relay;
datalayer_extended.cellpower.IO_state_discharge_enable = IO_state_discharge_enable;
datalayer_extended.cellpower.IO_state_IO_6 = IO_state_IO_6;
datalayer_extended.cellpower.IO_state_IO_7 = IO_state_IO_7;
datalayer_extended.cellpower.IO_state_IO_8 = IO_state_IO_8;
datalayer_extended.cellpower.error_Cell_overvoltage = error_Cell_overvoltage;
datalayer_extended.cellpower.error_Cell_undervoltage = error_Cell_undervoltage;
datalayer_extended.cellpower.error_Cell_end_of_life_voltage = error_Cell_end_of_life_voltage;
datalayer_extended.cellpower.error_Cell_voltage_misread = error_Cell_voltage_misread;
datalayer_extended.cellpower.error_Cell_over_temperature = error_Cell_over_temperature;
datalayer_extended.cellpower.error_Cell_under_temperature = error_Cell_under_temperature;
datalayer_extended.cellpower.error_Cell_unmanaged = error_Cell_unmanaged;
datalayer_extended.cellpower.error_LMU_over_temperature = error_LMU_over_temperature;
datalayer_extended.cellpower.error_LMU_under_temperature = error_LMU_under_temperature;
datalayer_extended.cellpower.error_Temp_sensor_open_circuit = error_Temp_sensor_open_circuit;
datalayer_extended.cellpower.error_Temp_sensor_short_circuit = error_Temp_sensor_short_circuit;
datalayer_extended.cellpower.error_SUB_communication = error_SUB_communication;
datalayer_extended.cellpower.error_LMU_communication = error_LMU_communication;
datalayer_extended.cellpower.error_Over_current_IN = error_Over_current_IN;
datalayer_extended.cellpower.error_Over_current_OUT = error_Over_current_OUT;
datalayer_extended.cellpower.error_Short_circuit = error_Short_circuit;
datalayer_extended.cellpower.error_Leak_detected = error_Leak_detected;
datalayer_extended.cellpower.error_Leak_detection_failed = error_Leak_detection_failed;
datalayer_extended.cellpower.error_Voltage_difference = error_Voltage_difference;
datalayer_extended.cellpower.error_BMCU_supply_over_voltage = error_BMCU_supply_over_voltage;
datalayer_extended.cellpower.error_BMCU_supply_under_voltage = error_BMCU_supply_under_voltage;
datalayer_extended.cellpower.error_Main_positive_contactor = error_Main_positive_contactor;
datalayer_extended.cellpower.error_Main_negative_contactor = error_Main_negative_contactor;
datalayer_extended.cellpower.error_Precharge_contactor = error_Precharge_contactor;
datalayer_extended.cellpower.error_Midpack_contactor = error_Midpack_contactor;
datalayer_extended.cellpower.error_Precharge_timeout = error_Precharge_timeout;
datalayer_extended.cellpower.error_Emergency_connector_override = error_Emergency_connector_override;
datalayer_extended.cellpower.warning_High_cell_voltage = warning_High_cell_voltage;
datalayer_extended.cellpower.warning_Low_cell_voltage = warning_Low_cell_voltage;
datalayer_extended.cellpower.warning_High_cell_temperature = warning_High_cell_temperature;
datalayer_extended.cellpower.warning_Low_cell_temperature = warning_Low_cell_temperature;
datalayer_extended.cellpower.warning_High_LMU_temperature = warning_High_LMU_temperature;
datalayer_extended.cellpower.warning_Low_LMU_temperature = warning_Low_LMU_temperature;
datalayer_extended.cellpower.warning_SUB_communication_interfered = warning_SUB_communication_interfered;
datalayer_extended.cellpower.warning_LMU_communication_interfered = warning_LMU_communication_interfered;
datalayer_extended.cellpower.warning_High_current_IN = warning_High_current_IN;
datalayer_extended.cellpower.warning_High_current_OUT = warning_High_current_OUT;
datalayer_extended.cellpower.warning_Pack_resistance_difference = warning_Pack_resistance_difference;
datalayer_extended.cellpower.warning_High_pack_resistance = warning_High_pack_resistance;
datalayer_extended.cellpower.warning_Cell_resistance_difference = warning_Cell_resistance_difference;
datalayer_extended.cellpower.warning_High_cell_resistance = warning_High_cell_resistance;
datalayer_extended.cellpower.warning_High_BMCU_supply_voltage = warning_High_BMCU_supply_voltage;
datalayer_extended.cellpower.warning_Low_BMCU_supply_voltage = warning_Low_BMCU_supply_voltage;
datalayer_extended.cellpower.warning_Low_SOC = warning_Low_SOC;
datalayer_extended.cellpower.warning_Balancing_required_OCV_model = warning_Balancing_required_OCV_model;
datalayer_extended.cellpower.warning_Charger_not_responding = warning_Charger_not_responding;
/* Peform safety checks */
if (system_state_chargecompleted) {
//TODO, shall we set max_charge_power_W to 0 incase this is true?
}
if (IO_state_charge_enable) {
//TODO, shall we react on this?
}
if (IO_state_discharge_enable) {
//TODO, shall we react on this?
}
if (error_state) {
//TODO, shall we react on this?
}
}
void receive_can_battery(CAN_frame rx_frame) {
switch (rx_frame.ID) {
case 0x1A4: //PDO1_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
cell_voltage_max_mV = (uint16_t)((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
cell_voltage_min_mV = (uint16_t)((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
pack_temperature_high_C = (int8_t)rx_frame.data.u8[4];
pack_temperature_low_C = (int8_t)rx_frame.data.u8[5];
system_state_discharge = (rx_frame.data.u8[6] & 0x01);
system_state_charge = ((rx_frame.data.u8[6] & 0x02) >> 1);
system_state_cellbalancing = ((rx_frame.data.u8[6] & 0x04) >> 2);
system_state_tricklecharge = ((rx_frame.data.u8[6] & 0x08) >> 3);
system_state_idle = ((rx_frame.data.u8[6] & 0x10) >> 4);
system_state_chargecompleted = ((rx_frame.data.u8[6] & 0x20) >> 5);
system_state_maintenancecharge = ((rx_frame.data.u8[6] & 0x40) >> 6);
IO_state_main_positive_relay = (rx_frame.data.u8[7] & 0x01);
IO_state_main_negative_relay = ((rx_frame.data.u8[7] & 0x02) >> 1);
IO_state_charge_enable = ((rx_frame.data.u8[7] & 0x04) >> 2);
IO_state_precharge_relay = ((rx_frame.data.u8[7] & 0x08) >> 3);
IO_state_discharge_enable = ((rx_frame.data.u8[7] & 0x10) >> 4);
IO_state_IO_6 = ((rx_frame.data.u8[7] & 0x20) >> 5);
IO_state_IO_7 = ((rx_frame.data.u8[7] & 0x40) >> 6);
IO_state_IO_8 = ((rx_frame.data.u8[7] & 0x80) >> 7);
break;
case 0x2A4: //PDO2_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
battery_pack_voltage_dV = (uint16_t)((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
battery_pack_current_dA = (int16_t)((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
battery_SOH_percentage = (uint8_t)rx_frame.data.u8[4];
battery_SOC_percentage = (uint8_t)rx_frame.data.u8[5];
battery_remaining_dAh = (uint16_t)((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]);
break;
case 0x3A4: //PDO3_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
cell_with_highest_voltage = (uint8_t)rx_frame.data.u8[0];
cell_with_lowest_voltage = (uint8_t)rx_frame.data.u8[1];
break;
case 0x4A4: //PDO4_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
error_Cell_overvoltage = (rx_frame.data.u8[0] & 0x01);
error_Cell_undervoltage = ((rx_frame.data.u8[0] & 0x02) >> 1);
error_Cell_end_of_life_voltage = ((rx_frame.data.u8[0] & 0x04) >> 2);
error_Cell_voltage_misread = ((rx_frame.data.u8[0] & 0x08) >> 3);
error_Cell_over_temperature = ((rx_frame.data.u8[0] & 0x10) >> 4);
error_Cell_under_temperature = ((rx_frame.data.u8[0] & 0x20) >> 5);
error_Cell_unmanaged = ((rx_frame.data.u8[0] & 0x40) >> 6);
error_LMU_over_temperature = ((rx_frame.data.u8[0] & 0x80) >> 7);
error_LMU_under_temperature = (rx_frame.data.u8[1] & 0x01);
error_Temp_sensor_open_circuit = ((rx_frame.data.u8[1] & 0x02) >> 1);
error_Temp_sensor_short_circuit = ((rx_frame.data.u8[1] & 0x04) >> 2);
error_SUB_communication = ((rx_frame.data.u8[1] & 0x08) >> 3);
error_LMU_communication = ((rx_frame.data.u8[1] & 0x10) >> 4);
error_Over_current_IN = ((rx_frame.data.u8[1] & 0x20) >> 5);
error_Over_current_OUT = ((rx_frame.data.u8[1] & 0x40) >> 6);
error_Short_circuit = ((rx_frame.data.u8[1] & 0x80) >> 7);
error_Leak_detected = (rx_frame.data.u8[2] & 0x01);
error_Leak_detection_failed = ((rx_frame.data.u8[2] & 0x02) >> 1);
error_Voltage_difference = ((rx_frame.data.u8[2] & 0x04) >> 2);
error_BMCU_supply_over_voltage = ((rx_frame.data.u8[2] & 0x08) >> 3);
error_BMCU_supply_under_voltage = ((rx_frame.data.u8[2] & 0x10) >> 4);
error_Main_positive_contactor = ((rx_frame.data.u8[2] & 0x20) >> 5);
error_Main_negative_contactor = ((rx_frame.data.u8[2] & 0x40) >> 6);
error_Precharge_contactor = ((rx_frame.data.u8[2] & 0x80) >> 7);
error_Midpack_contactor = (rx_frame.data.u8[3] & 0x01);
error_Precharge_timeout = ((rx_frame.data.u8[3] & 0x02) >> 1);
error_Emergency_connector_override = ((rx_frame.data.u8[3] & 0x04) >> 2);
warning_High_cell_voltage = (rx_frame.data.u8[4] & 0x01);
warning_Low_cell_voltage = ((rx_frame.data.u8[4] & 0x02) >> 1);
warning_High_cell_temperature = ((rx_frame.data.u8[4] & 0x04) >> 2);
warning_Low_cell_temperature = ((rx_frame.data.u8[4] & 0x08) >> 3);
warning_High_LMU_temperature = ((rx_frame.data.u8[4] & 0x10) >> 4);
warning_Low_LMU_temperature = ((rx_frame.data.u8[4] & 0x20) >> 5);
warning_SUB_communication_interfered = ((rx_frame.data.u8[4] & 0x40) >> 6);
warning_LMU_communication_interfered = ((rx_frame.data.u8[4] & 0x80) >> 7);
warning_High_current_IN = (rx_frame.data.u8[5] & 0x01);
warning_High_current_OUT = ((rx_frame.data.u8[5] & 0x02) >> 1);
warning_Pack_resistance_difference = ((rx_frame.data.u8[5] & 0x04) >> 2);
warning_High_pack_resistance = ((rx_frame.data.u8[5] & 0x08) >> 3);
warning_Cell_resistance_difference = ((rx_frame.data.u8[5] & 0x10) >> 4);
warning_High_cell_resistance = ((rx_frame.data.u8[5] & 0x20) >> 5);
warning_High_BMCU_supply_voltage = ((rx_frame.data.u8[5] & 0x40) >> 6);
warning_Low_BMCU_supply_voltage = ((rx_frame.data.u8[5] & 0x80) >> 7);
warning_Low_SOC = (rx_frame.data.u8[6] & 0x01);
warning_Balancing_required_OCV_model = ((rx_frame.data.u8[6] & 0x02) >> 1);
warning_Charger_not_responding = ((rx_frame.data.u8[6] & 0x04) >> 2);
break;
case 0x7A4: //PDO7_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
requested_charge_current_dA = (uint16_t)((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]);
average_charge_current_dA = (uint16_t)((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]);
actual_charge_current_dA = (uint16_t)((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]);
requested_exceeding_average_current = (rx_frame.data.u8[6] & 0x01);
break;
case 0x7A5: //PDO7.1_TX - 200ms
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
error_state = (rx_frame.data.u8[0] & 0x01);
break;
default:
break;
}
}
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 1s CAN Message
if (currentMillis - previousMillis1s >= INTERVAL_1_S) {
previousMillis1s = currentMillis;
/*
transmit_can(&CELLPOWER_18FF50E9, can_config.battery);
transmit_can(&CELLPOWER_18FF50E8, can_config.battery);
transmit_can(&CELLPOWER_18FF50E7, can_config.battery);
transmit_can(&CELLPOWER_18FF50E5, can_config.battery);
*/
}
}
void setup_battery(void) { // Performs one time setup at startup
#ifdef DEBUG_VIA_USB
Serial.println("Cellpower BMS selected");
#endif
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
}
#endif // CELLPOWER_BMS

View file

@ -0,0 +1,19 @@
#ifndef CELLPOWER_BMS_H
#define CELLPOWER_BMS_H
#include <Arduino.h>
#include "../include.h"
/* Tweak these according to your battery build */
#define MAX_PACK_VOLTAGE_DV 5000 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 1500
#define MAX_CELL_VOLTAGE_MV 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
/* Do not modify any rows below*/
#define BATTERY_SELECTED
#define NATIVECAN_250KBPS
void setup_battery(void);
void transmit_can(CAN_frame* tx_frame, int interface);
#endif

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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);
} }
} }

View file

@ -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

View file

@ -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

View file

@ -145,8 +145,8 @@ typedef struct {
int64_t time_comm_us = 0; int64_t time_comm_us = 0;
/** 10 ms function measurement variable */ /** 10 ms function measurement variable */
int64_t time_10ms_us = 0; int64_t time_10ms_us = 0;
/** 5 s function measurement variable */ /** Value update function measurement variable */
int64_t time_5s_us = 0; int64_t time_values_us = 0;
/** CAN TX function measurement variable */ /** CAN TX function measurement variable */
int64_t time_cantx_us = 0; int64_t time_cantx_us = 0;
@ -163,9 +163,9 @@ typedef struct {
*/ */
int64_t time_snap_10ms_us = 0; int64_t time_snap_10ms_us = 0;
/** Function measurement snapshot variable. /** Function measurement snapshot variable.
* This will show the performance of the 5 s functionality of the core task when the total time reached a new worst case * This will show the performance of the values functionality of the core task when the total time reached a new worst case
*/ */
int64_t time_snap_5s_us = 0; int64_t time_snap_values_us = 0;
/** Function measurement snapshot variable. /** Function measurement snapshot variable.
* This will show the performance of CAN TX when the total time reached a new worst case * This will show the performance of CAN TX when the total time reached a new worst case
*/ */

View file

@ -71,6 +71,91 @@ typedef struct {
uint8_t ST_cold_shutoff_valve = 0; uint8_t ST_cold_shutoff_valve = 0;
} DATALAYER_INFO_BMWI3; } DATALAYER_INFO_BMWI3;
typedef struct {
/** uint16_t */
/** SOC% estimate. Estimated from total pack voltage */
uint16_t SOC_estimated = 0;
/** uint16_t */
/** SOC% raw battery value. Highprecision. Can be locked if pack is crashed */
uint16_t SOC_highprec = 0;
/** uint16_t */
/** SOC% polled OBD2 value. Can be locked if pack is crashed */
uint16_t SOC_polled = 0;
/** uint16_t */
/** Voltage raw battery value */
uint16_t voltage_periodic = 0;
/** uint16_t */
/** Voltage polled OBD2*/
uint16_t voltage_polled = 0;
} DATALAYER_INFO_BYDATTO3;
typedef struct {
/** bool */
/** All values either True or false */
bool system_state_discharge = false;
bool system_state_charge = false;
bool system_state_cellbalancing = false;
bool system_state_tricklecharge = false;
bool system_state_idle = false;
bool system_state_chargecompleted = false;
bool system_state_maintenancecharge = false;
bool IO_state_main_positive_relay = false;
bool IO_state_main_negative_relay = false;
bool IO_state_charge_enable = false;
bool IO_state_precharge_relay = false;
bool IO_state_discharge_enable = false;
bool IO_state_IO_6 = false;
bool IO_state_IO_7 = false;
bool IO_state_IO_8 = false;
bool error_Cell_overvoltage = false;
bool error_Cell_undervoltage = false;
bool error_Cell_end_of_life_voltage = false;
bool error_Cell_voltage_misread = false;
bool error_Cell_over_temperature = false;
bool error_Cell_under_temperature = false;
bool error_Cell_unmanaged = false;
bool error_LMU_over_temperature = false;
bool error_LMU_under_temperature = false;
bool error_Temp_sensor_open_circuit = false;
bool error_Temp_sensor_short_circuit = false;
bool error_SUB_communication = false;
bool error_LMU_communication = false;
bool error_Over_current_IN = false;
bool error_Over_current_OUT = false;
bool error_Short_circuit = false;
bool error_Leak_detected = false;
bool error_Leak_detection_failed = false;
bool error_Voltage_difference = false;
bool error_BMCU_supply_over_voltage = false;
bool error_BMCU_supply_under_voltage = false;
bool error_Main_positive_contactor = false;
bool error_Main_negative_contactor = false;
bool error_Precharge_contactor = false;
bool error_Midpack_contactor = false;
bool error_Precharge_timeout = false;
bool error_Emergency_connector_override = false;
bool warning_High_cell_voltage = false;
bool warning_Low_cell_voltage = false;
bool warning_High_cell_temperature = false;
bool warning_Low_cell_temperature = false;
bool warning_High_LMU_temperature = false;
bool warning_Low_LMU_temperature = false;
bool warning_SUB_communication_interfered = false;
bool warning_LMU_communication_interfered = false;
bool warning_High_current_IN = false;
bool warning_High_current_OUT = false;
bool warning_Pack_resistance_difference = false;
bool warning_High_pack_resistance = false;
bool warning_Cell_resistance_difference = false;
bool warning_High_cell_resistance = false;
bool warning_High_BMCU_supply_voltage = false;
bool warning_Low_BMCU_supply_voltage = false;
bool warning_Low_SOC = false;
bool warning_Balancing_required_OCV_model = false;
bool warning_Charger_not_responding = false;
} DATALAYER_INFO_CELLPOWER;
typedef struct { typedef struct {
/** uint8_t */ /** uint8_t */
/** Contactor status */ /** Contactor status */
@ -142,12 +227,61 @@ 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_BMWIX bmwix; DATALAYER_INFO_BMWIX bmwix;
DATALAYER_INFO_BMWI3 bmwi3; DATALAYER_INFO_BMWI3 bmwi3;
DATALAYER_INFO_BYDATTO3 bydAtto3;
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;

View file

@ -15,7 +15,10 @@ PubSubClient client(espClient);
char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; char mqtt_msg[MQTT_MSG_BUFFER_SIZE];
MyTimer publish_global_timer(5000); //publish timer MyTimer publish_global_timer(5000); //publish timer
MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical. MyTimer check_global_timer(800); // check timmer - low-priority MQTT checks, where responsiveness is not critical.
static const char* hostname = WiFi.getHostname();
static String topic_name = "";
static String object_id_prefix = "";
static String device_name = "";
// Tracking reconnection attempts and failures // Tracking reconnection attempts and failures
static unsigned long lastReconnectAttempt = 0; static unsigned long lastReconnectAttempt = 0;
@ -44,41 +47,58 @@ struct SensorConfig {
}; };
SensorConfig sensorConfigs[] = { SensorConfig sensorConfigs[] = {
{"SOC", "Battery Emulator SOC (scaled)", "{{ value_json.SOC }}", "%", "battery"}, {"SOC", "SOC (scaled)", "{{ value_json.SOC }}", "%", "battery"},
{"SOC_real", "Battery Emulator SOC (real)", "{{ value_json.SOC_real }}", "%", "battery"}, {"SOC_real", "SOC (real)", "{{ value_json.SOC_real }}", "%", "battery"},
{"state_of_health", "Battery Emulator State Of Health", "{{ value_json.state_of_health }}", "%", "battery"}, {"state_of_health", "State Of Health", "{{ value_json.state_of_health }}", "%", "battery"},
{"temperature_min", "Battery Emulator Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"}, {"temperature_min", "Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"},
{"temperature_max", "Battery Emulator Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"}, {"temperature_max", "Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"},
{"stat_batt_power", "Battery Emulator Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"}, {"stat_batt_power", "Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"},
{"battery_current", "Battery Emulator Battery Current", "{{ value_json.battery_current }}", "A", "current"}, {"battery_current", "Battery Current", "{{ value_json.battery_current }}", "A", "current"},
{"cell_max_voltage", "Battery Emulator Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"}, {"cell_max_voltage", "Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"},
{"cell_min_voltage", "Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, {"cell_min_voltage", "Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"},
{"battery_voltage", "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, {"battery_voltage", "Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"},
{"total_capacity", "Battery Emulator Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"}, {"total_capacity", "Battery Total Capacity", "{{ value_json.total_capacity }}", "Wh", "energy"},
{"remaining_capacity", "Battery Emulator Battery Remaining Capacity (scaled)", {"remaining_capacity", "Battery Remaining Capacity (scaled)", "{{ value_json.remaining_capacity }}", "Wh",
"{{ value_json.remaining_capacity }}", "Wh", "energy"}, "energy"},
{"remaining_capacity_real", "Battery Emulator Battery Remaining Capacity (real)", {"remaining_capacity_real", "Battery Remaining Capacity (real)", "{{ value_json.remaining_capacity_real }}", "Wh",
"{{ value_json.remaining_capacity_real }}", "Wh", "energy"}, "energy"},
{"max_discharge_power", "Battery Emulator Battery Max Discharge Power", "{{ value_json.max_discharge_power }}", "W", {"max_discharge_power", "Battery Max Discharge Power", "{{ value_json.max_discharge_power }}", "W", "power"},
"power"}, {"max_charge_power", "Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", "power"},
{"max_charge_power", "Battery Emulator Battery Max Charge Power", "{{ value_json.max_charge_power }}", "W", {"bms_status", "BMS Status", "{{ value_json.bms_status }}", "", ""},
"power"}, {"pause_status", "Pause Status", "{{ value_json.pause_status }}", "", ""},
{"bms_status", "Battery Emulator BMS Status", "{{ value_json.bms_status }}", "", ""}, #ifdef DOUBLE_BATTERY
{"pause_status", "Battery Emulator Pause Status", "{{ value_json.pause_status }}", "", ""}, {"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, const char* hostname) { static String generateCommonInfoAutoConfigTopic(const char* object_id) {
return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/" + String(object_id) + "/config"; return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config";
} }
static String generateCellVoltageAutoConfigTopic(int cell_number, const char* hostname) { static String generateCellVoltageAutoConfigTopic(int cell_number, String battery_suffix) {
return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/cell_voltage" + String(cell_number) + return "homeassistant/sensor/" + topic_name + "/cell_voltage" + battery_suffix + String(cell_number) + "/config";
"/config";
} }
static String generateEventsAutoConfigTopic(const char* object_id, const char* hostname) { static String generateEventsAutoConfigTopic(const char* object_id) {
return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/" + String(object_id) + "/config"; return "homeassistant/sensor/" + topic_name + "/" + String(object_id) + "/config";
} }
#endif // HA_AUTODISCOVERY #endif // HA_AUTODISCOVERY
@ -90,7 +110,7 @@ static void publish_common_info(void) {
#ifdef HA_AUTODISCOVERY #ifdef HA_AUTODISCOVERY
static bool mqtt_first_transmission = true; static bool mqtt_first_transmission = true;
#endif // HA_AUTODISCOVERY #endif // HA_AUTODISCOVERY
static String state_topic = String("battery-emulator_") + String(hostname) + "/info"; static String state_topic = topic_name + "/info";
#ifdef HA_AUTODISCOVERY #ifdef HA_AUTODISCOVERY
if (mqtt_first_transmission == true) { if (mqtt_first_transmission == true) {
mqtt_first_transmission = false; mqtt_first_transmission = false;
@ -98,8 +118,8 @@ static void publish_common_info(void) {
SensorConfig& config = sensorConfigs[i]; SensorConfig& config = sensorConfigs[i];
doc["name"] = config.name; doc["name"] = config.name;
doc["state_topic"] = state_topic; doc["state_topic"] = state_topic;
doc["unique_id"] = "battery-emulator_" + String(hostname) + "_" + String(config.object_id); doc["unique_id"] = topic_name + "_" + String(config.object_id);
doc["object_id"] = String(hostname) + "_" + String(config.object_id); doc["object_id"] = object_id_prefix + String(config.object_id);
doc["value_template"] = config.value_template; doc["value_template"] = config.value_template;
if (config.unit != nullptr && strlen(config.unit) > 0) if (config.unit != nullptr && strlen(config.unit) > 0)
doc["unit_of_measurement"] = config.unit; doc["unit_of_measurement"] = config.unit;
@ -112,12 +132,12 @@ static void publish_common_info(void) {
doc["device"]["identifiers"][0] = "battery-emulator"; doc["device"]["identifiers"][0] = "battery-emulator";
doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["manufacturer"] = "DalaTech";
doc["device"]["model"] = "BatteryEmulator"; doc["device"]["model"] = "BatteryEmulator";
doc["device"]["name"] = "BatteryEmulator_" + String(hostname); doc["device"]["name"] = device_name;
doc["origin"]["name"] = "BatteryEmulator"; doc["origin"]["name"] = "BatteryEmulator";
doc["origin"]["sw"] = String(version_number) + "-mqtt"; doc["origin"]["sw"] = String(version_number) + "-mqtt";
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
serializeJson(doc, mqtt_msg); serializeJson(doc, mqtt_msg);
mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id, hostname).c_str(), mqtt_msg, true); mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id).c_str(), mqtt_msg, true);
doc.clear(); doc.clear();
} }
@ -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
@ -166,49 +209,80 @@ static void publish_cell_voltages(void) {
static bool mqtt_first_transmission = true; static bool mqtt_first_transmission = true;
#endif // HA_AUTODISCOVERY #endif // HA_AUTODISCOVERY
static JsonDocument doc; static JsonDocument doc;
static String state_topic = String("battery-emulator_") + String(hostname) + "/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"] = "battery_voltage_cell" + String(cellNumber);
doc["unique_id"] = "battery-emulator_" + String(hostname) + "_battery_voltage_cell" +
String(cellNumber); //"battery-emulator_" + String(hostname) + "_" +
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"] = "BatteryEmulator_" + String(hostname);
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, hostname).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) {
@ -223,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() {
@ -234,15 +327,15 @@ void publish_events() {
#ifdef HA_AUTODISCOVERY #ifdef HA_AUTODISCOVERY
static bool mqtt_first_transmission = true; static bool mqtt_first_transmission = true;
#endif // HA_AUTODISCOVERY #endif // HA_AUTODISCOVERY
static String state_topic = String("battery-emulator_") + String(hostname) + "/events"; static String state_topic = topic_name + "/events";
#ifdef HA_AUTODISCOVERY #ifdef HA_AUTODISCOVERY
if (mqtt_first_transmission == true) { if (mqtt_first_transmission == true) {
mqtt_first_transmission = false; mqtt_first_transmission = false;
doc["name"] = "Battery Emulator Event"; doc["name"] = "Event";
doc["state_topic"] = state_topic; doc["state_topic"] = state_topic;
doc["unique_id"] = "battery-emulator_" + String(hostname) + "_event"; doc["unique_id"] = topic_name + "_event";
doc["object_id"] = String(hostname) + "_event"; doc["object_id"] = object_id_prefix + "event";
doc["value_template"] = doc["value_template"] =
"{{ value_json.event_type ~ ' (c:' ~ value_json.count ~ ',m:' ~ value_json.millis ~ ') ' ~ value_json.message " "{{ value_json.event_type ~ ' (c:' ~ value_json.count ~ ',m:' ~ value_json.millis ~ ') ' ~ value_json.message "
"}}"; "}}";
@ -252,12 +345,12 @@ void publish_events() {
doc["device"]["identifiers"][0] = "battery-emulator"; doc["device"]["identifiers"][0] = "battery-emulator";
doc["device"]["manufacturer"] = "DalaTech"; doc["device"]["manufacturer"] = "DalaTech";
doc["device"]["model"] = "BatteryEmulator"; doc["device"]["model"] = "BatteryEmulator";
doc["device"]["name"] = "BatteryEmulator_" + String(hostname); doc["device"]["name"] = device_name;
doc["origin"]["name"] = "BatteryEmulator"; doc["origin"]["name"] = "BatteryEmulator";
doc["origin"]["sw"] = String(version_number) + "-mqtt"; doc["origin"]["sw"] = String(version_number) + "-mqtt";
doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator"; doc["origin"]["url"] = "https://github.com/dalathegreat/Battery-Emulator";
serializeJson(doc, mqtt_msg); serializeJson(doc, mqtt_msg);
mqtt_publish(generateEventsAutoConfigTopic("event", hostname).c_str(), mqtt_msg, true); mqtt_publish(generateEventsAutoConfigTopic("event").c_str(), mqtt_msg, true);
doc.clear(); doc.clear();
} else { } else {
@ -313,7 +406,7 @@ static bool reconnect() {
Serial.print("Attempting MQTT connection... "); Serial.print("Attempting MQTT connection... ");
#endif // DEBUG_VIA_USB #endif // DEBUG_VIA_USB
char clientId[64]; // Adjust the size as needed char clientId[64]; // Adjust the size as needed
snprintf(clientId, sizeof(clientId), "LilyGoClient-%s", hostname); snprintf(clientId, sizeof(clientId), "BatteryEmulatorClient-%s", WiFi.getHostname());
// Attempt to connect // Attempt to connect
if (client.connect(clientId, mqtt_user, mqtt_password)) { if (client.connect(clientId, mqtt_user, mqtt_password)) {
connected_once = true; connected_once = true;
@ -339,6 +432,22 @@ static bool reconnect() {
} }
void init_mqtt(void) { void init_mqtt(void) {
#ifdef MQTT
#ifdef MQTT_MANUAL_TOPIC_OBJECT_NAME
// Use custom topic name, object ID prefix, and device name from user settings
topic_name = mqtt_topic_name;
object_id_prefix = mqtt_object_id_prefix;
device_name = mqtt_device_name;
#else
// Use default naming based on WiFi hostname for topic, object ID prefix, and device name
topic_name = "battery-emulator_" + String(WiFi.getHostname());
object_id_prefix = String(WiFi.getHostname()) + String("_");
device_name = "BatteryEmulator_" + String(WiFi.getHostname());
#endif
#endif
client.setServer(MQTT_SERVER, MQTT_PORT); client.setServer(MQTT_SERVER, MQTT_PORT);
#ifdef DEBUG_VIA_USB #ifdef DEBUG_VIA_USB
Serial.println("MQTT initialized"); Serial.println("MQTT initialized");

View file

@ -44,6 +44,9 @@ extern const char* version_number; // The current software version, used for mq
extern const char* mqtt_user; extern const char* mqtt_user;
extern const char* mqtt_password; extern const char* mqtt_password;
extern const char* mqtt_topic_name;
extern const char* mqtt_object_id_prefix;
extern const char* mqtt_device_name;
extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE]; extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE];

View file

@ -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);

View file

@ -6,7 +6,7 @@
#define MAX_CAN_FAILURES 50 #define MAX_CAN_FAILURES 50
#define MAX_CHARGE_DISCHARGE_LIMIT_FAILURES 1 #define MAX_CHARGE_DISCHARGE_LIMIT_FAILURES 5
//battery pause status begin //battery pause status begin
enum battery_pause_status { NORMAL = 0, PAUSING = 1, PAUSED = 2, RESUMING = 3 }; enum battery_pause_status { NORMAL = 0, PAUSING = 1, PAUSED = 2, RESUMING = 3 };

View file

@ -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:

View file

@ -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) \

View file

@ -33,8 +33,8 @@ enum led_color { GREEN, YELLOW, RED, BLUE, RGB };
#define INTERVAL_200_MS_DELAYED 240 #define INTERVAL_200_MS_DELAYED 240
#define INTERVAL_500_MS_DELAYED 550 #define INTERVAL_500_MS_DELAYED 550
#define CAN_STILL_ALIVE 12 #define CAN_STILL_ALIVE 60
// Set by battery each time we get a CAN message. Decrements every 5seconds. When reaching 0, sets event // Set by battery each time we get a CAN message. Decrements every second. When reaching 0, sets event
/* CAN Frame structure */ /* CAN Frame structure */
typedef struct { typedef struct {

View file

@ -16,48 +16,6 @@ String advanced_battery_processor(const String& var) {
// Start a new block with a specific background color // Start a new block with a specific background color
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>"; content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
#ifdef BMW_IX_BATTERY
content += "<h4>Battery Voltage after Contactor: " + String(datalayer_extended.bmwix.battery_voltage_after_contactor) + " dV</h4>";
content += "<h4>Max Design Voltage: " + String(datalayer.battery.info.max_design_voltage_dV) + " dV</h4>";
content += "<h4>Min Design Voltage: " + String(datalayer.battery.info.min_design_voltage_dV) + " dV</h4>";
content += "<h4>Max Cell Design Voltage: " + String(datalayer.battery.info.max_cell_voltage_mV) + " mV</h4>";
content += "<h4>Min Cell Design Voltage: " + String(datalayer.battery.info.min_cell_voltage_mV) + " mV</h4>";
content += "<h4>Min Cell Voltage Data Age: " + String(datalayer_extended.bmwix.min_cell_voltage_data_age) + " ms</h4>";
content += "<h4>Max Cell Voltage Data Age: " + String(datalayer_extended.bmwix.max_cell_voltage_data_age) + " ms</h4>";
content += "<h4>Currently allowed Discharge Power: " + String(datalayer.battery.status.max_discharge_power_W) + " W</h4>";
content += "<h4>Currently allowed Charge Power: " + String(datalayer.battery.status.max_charge_power_W) + " W</h4>";
content += "<h4>T30 Terminal Voltage: " + String(datalayer_extended.bmwix.T30_Voltage) + " mV</h4>";
content += "<h4>Detected Cell Count: " + String(datalayer.battery.info.number_of_cells) + "</h4>";
static const char* balanceText[5] = {"0 No balancing mode active",
"1 Voltage-Controlled Balancing Mode",
"2 Time-Controlled Balancing Mode with Demand Calculation at End of Charging" ,
"3 Time-Controlled Balancing Mode with Demand Calculation at Resting Voltage" ,
"4 No balancing mode active, qualifier invalid"
};
content += "<h4>Balancing Status: " + String((balanceText[datalayer_extended.bmwix.balancing_status])) + "</h4>";
static const char* hvilText[2] = {"Error (Loop Open)",
"OK (Loop Closed)"};
content += "<h4>HVIL Status: " + String(hvilText[datalayer_extended.bmwix.hvil_status]) + "</h4>";
content += "<h4>BMS Uptime: " + String(datalayer_extended.bmwix.bms_uptime) + " seconds</h4>";
content += "<h4>BMS Allowed Charge Amps: " + String(datalayer_extended.bmwix.allowable_charge_amps) + " A</h4>";
content += "<h4>BMS Allowed Disharge Amps: " + String(datalayer_extended.bmwix.allowable_discharge_amps) + " A</h4>";
content += "<br>";
content += "<h3>HV Isolation (2147483647kOhm = maximum/invalid)</h3>";
content += "<h4>Isolation Positive: " + String(datalayer_extended.bmwix.iso_safety_positive) + " kOhm</h4>";
content += "<h4>Isolation Negative: " + String(datalayer_extended.bmwix.iso_safety_negative) + " kOhm</h4>";
content += "<h4>Isolation Parallel: " + String(datalayer_extended.bmwix.iso_safety_parallel) + " kOhm</h4>";
static const char* pyroText[5] = {"0 Value Invalid",
"1 Successfully Blown",
"2 Disconnected" ,
"3 Not Activated - Pyro Intact" ,
"4 Unknown"
};
content += "<h4>Pyro Status PSS1: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss1])) + "</h4>";
content += "<h4>Pyro Status PSS4: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss4])) + "</h4>";
content += "<h4>Pyro Status PSS6: " + String((pyroText[datalayer_extended.bmwix.pyro_status_pss6])) + "</h4>";
#endif //BMW_IX_BATTERY
#ifdef BMW_I3_BATTERY #ifdef BMW_I3_BATTERY
content += "<h4>SOC raw: " + String(datalayer_extended.bmwi3.SOC_raw) + "</h4>"; content += "<h4>SOC raw: " + String(datalayer_extended.bmwi3.SOC_raw) + "</h4>";
content += "<h4>SOC dash: " + String(datalayer_extended.bmwi3.SOC_dash) + "</h4>"; content += "<h4>SOC dash: " + String(datalayer_extended.bmwi3.SOC_dash) + "</h4>";
@ -142,6 +100,149 @@ String advanced_battery_processor(const String& var) {
#endif //BMW_I3_BATTERY #endif //BMW_I3_BATTERY
#ifdef CELLPOWER_BMS
static const char* falseTrue[2] = {"False", "True"};
content += "<h3>States:</h3>";
content += "<h4>Discharge: " + String(falseTrue[datalayer_extended.cellpower.system_state_discharge]) + "</h4>";
content += "<h4>Charge: " + String(falseTrue[datalayer_extended.cellpower.system_state_charge]) + "</h4>";
content +=
"<h4>Cellbalancing: " + String(falseTrue[datalayer_extended.cellpower.system_state_cellbalancing]) + "</h4>";
content +=
"<h4>Tricklecharging: " + String(falseTrue[datalayer_extended.cellpower.system_state_tricklecharge]) + "</h4>";
content += "<h4>Idle: " + String(falseTrue[datalayer_extended.cellpower.system_state_idle]) + "</h4>";
content += "<h4>Charge completed: " + String(falseTrue[datalayer_extended.cellpower.system_state_chargecompleted]) +
"</h4>";
content +=
"<h4>Maintenance charge: " + String(falseTrue[datalayer_extended.cellpower.system_state_maintenancecharge]) +
"</h4>";
content += "<h3>IO:</h3>";
content +=
"<h4>Main positive relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_main_positive_relay]) +
"</h4>";
content +=
"<h4>Main negative relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_main_negative_relay]) +
"</h4>";
content +=
"<h4>Charge enabled: " + String(falseTrue[datalayer_extended.cellpower.IO_state_charge_enable]) + "</h4>";
content +=
"<h4>Precharge relay: " + String(falseTrue[datalayer_extended.cellpower.IO_state_precharge_relay]) + "</h4>";
content +=
"<h4>Discharge enable: " + String(falseTrue[datalayer_extended.cellpower.IO_state_discharge_enable]) + "</h4>";
content += "<h4>IO 6: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_6]) + "</h4>";
content += "<h4>IO 7: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_7]) + "</h4>";
content += "<h4>IO 8: " + String(falseTrue[datalayer_extended.cellpower.IO_state_IO_8]) + "</h4>";
content += "<h3>Errors:</h3>";
content +=
"<h4>Cell overvoltage: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_overvoltage]) + "</h4>";
content +=
"<h4>Cell undervoltage: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_undervoltage]) + "</h4>";
content += "<h4>Cell end of life voltage: " +
String(falseTrue[datalayer_extended.cellpower.error_Cell_end_of_life_voltage]) + "</h4>";
content +=
"<h4>Cell voltage misread: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_voltage_misread]) +
"</h4>";
content +=
"<h4>Cell over temperature: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_over_temperature]) +
"</h4>";
content +=
"<h4>Cell under temperature: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_under_temperature]) +
"</h4>";
content += "<h4>Cell unmanaged: " + String(falseTrue[datalayer_extended.cellpower.error_Cell_unmanaged]) + "</h4>";
content +=
"<h4>LMU over temperature: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_over_temperature]) +
"</h4>";
content +=
"<h4>LMU under temperature: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_under_temperature]) +
"</h4>";
content += "<h4>Temp sensor open circuit: " +
String(falseTrue[datalayer_extended.cellpower.error_Temp_sensor_open_circuit]) + "</h4>";
content += "<h4>Temp sensor short circuit: " +
String(falseTrue[datalayer_extended.cellpower.error_Temp_sensor_short_circuit]) + "</h4>";
content += "<h4>SUB comm: " + String(falseTrue[datalayer_extended.cellpower.error_SUB_communication]) + "</h4>";
content += "<h4>LMU comm: " + String(falseTrue[datalayer_extended.cellpower.error_LMU_communication]) + "</h4>";
content +=
"<h4>Over current In: " + String(falseTrue[datalayer_extended.cellpower.error_Over_current_IN]) + "</h4>";
content +=
"<h4>Over current Out: " + String(falseTrue[datalayer_extended.cellpower.error_Over_current_OUT]) + "</h4>";
content += "<h4>Short circuit: " + String(falseTrue[datalayer_extended.cellpower.error_Short_circuit]) + "</h4>";
content += "<h4>Leak detected: " + String(falseTrue[datalayer_extended.cellpower.error_Leak_detected]) + "</h4>";
content +=
"<h4>Leak detection failed: " + String(falseTrue[datalayer_extended.cellpower.error_Leak_detection_failed]) +
"</h4>";
content +=
"<h4>Voltage diff: " + String(falseTrue[datalayer_extended.cellpower.error_Voltage_difference]) + "</h4>";
content += "<h4>BMCU supply overvoltage: " +
String(falseTrue[datalayer_extended.cellpower.error_BMCU_supply_over_voltage]) + "</h4>";
content += "<h4>BMCU supply undervoltage: " +
String(falseTrue[datalayer_extended.cellpower.error_BMCU_supply_under_voltage]) + "</h4>";
content += "<h4>Main positive contactor: " +
String(falseTrue[datalayer_extended.cellpower.error_Main_positive_contactor]) + "</h4>";
content += "<h4>Main negative contactor: " +
String(falseTrue[datalayer_extended.cellpower.error_Main_negative_contactor]) + "</h4>";
content += "<h4>Precharge contactor: " + String(falseTrue[datalayer_extended.cellpower.error_Precharge_contactor]) +
"</h4>";
content +=
"<h4>Midpack contactor: " + String(falseTrue[datalayer_extended.cellpower.error_Midpack_contactor]) + "</h4>";
content +=
"<h4>Precharge timeout: " + String(falseTrue[datalayer_extended.cellpower.error_Precharge_timeout]) + "</h4>";
content += "<h4>EMG connector override: " +
String(falseTrue[datalayer_extended.cellpower.error_Emergency_connector_override]) + "</h4>";
content += "<h3>Warnings:</h3>";
content +=
"<h4>High cell voltage: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_voltage]) + "</h4>";
content +=
"<h4>Low cell voltage: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_cell_voltage]) + "</h4>";
content +=
"<h4>High cell temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_temperature]) +
"</h4>";
content +=
"<h4>Low cell temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_cell_temperature]) +
"</h4>";
content +=
"<h4>High LMU temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_High_LMU_temperature]) +
"</h4>";
content +=
"<h4>Low LMU temperature: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_LMU_temperature]) +
"</h4>";
content +=
"<h4>SUB comm interf: " + String(falseTrue[datalayer_extended.cellpower.warning_SUB_communication_interfered]) +
"</h4>";
content +=
"<h4>LMU comm interf: " + String(falseTrue[datalayer_extended.cellpower.warning_LMU_communication_interfered]) +
"</h4>";
content +=
"<h4>High current In: " + String(falseTrue[datalayer_extended.cellpower.warning_High_current_IN]) + "</h4>";
content +=
"<h4>High current Out: " + String(falseTrue[datalayer_extended.cellpower.warning_High_current_OUT]) + "</h4>";
content += "<h4>Pack resistance diff: " +
String(falseTrue[datalayer_extended.cellpower.warning_Pack_resistance_difference]) + "</h4>";
content +=
"<h4>High pack resistance: " + String(falseTrue[datalayer_extended.cellpower.warning_High_pack_resistance]) +
"</h4>";
content += "<h4>Cell resistance diff: " +
String(falseTrue[datalayer_extended.cellpower.warning_Cell_resistance_difference]) + "</h4>";
content +=
"<h4>High cell resistance: " + String(falseTrue[datalayer_extended.cellpower.warning_High_cell_resistance]) +
"</h4>";
content += "<h4>High BMCU supply voltage: " +
String(falseTrue[datalayer_extended.cellpower.warning_High_BMCU_supply_voltage]) + "</h4>";
content += "<h4>Low BMCU supply voltage: " +
String(falseTrue[datalayer_extended.cellpower.warning_Low_BMCU_supply_voltage]) + "</h4>";
content += "<h4>Low SOC: " + String(falseTrue[datalayer_extended.cellpower.warning_Low_SOC]) + "</h4>";
content += "<h4>Balancing required: " +
String(falseTrue[datalayer_extended.cellpower.warning_Balancing_required_OCV_model]) + "</h4>";
content += "<h4>Charger not responding: " +
String(falseTrue[datalayer_extended.cellpower.warning_Charger_not_responding]) + "</h4>";
#endif //CELLPOWER_BMS
#ifdef BYD_ATTO_3_BATTERY
content += "<h4>SOC estimated: " + String(datalayer_extended.bydAtto3.SOC_estimated) + "</h4>";
content += "<h4>SOC highprec: " + String(datalayer_extended.bydAtto3.SOC_highprec) + "</h4>";
content += "<h4>SOC OBD2: " + String(datalayer_extended.bydAtto3.SOC_polled) + "</h4>";
content += "<h4>Voltage periodic: " + String(datalayer_extended.bydAtto3.voltage_periodic) + "</h4>";
content += "<h4>Voltage OBD2: " + String(datalayer_extended.bydAtto3.voltage_polled) + "</h4>";
#endif //BYD_ATTO_3_BATTERY
#ifdef TESLA_BATTERY #ifdef TESLA_BATTERY
static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING", static const char* contactorText[] = {"UNKNOWN(0)", "OPEN", "CLOSING", "BLOCKED", "OPENING",
"CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL", "CLOSED", "UNKNOWN(6)", "WELDED", "POS_CL", "NEG_CL",
@ -193,8 +294,55 @@ String advanced_battery_processor(const String& var) {
content += "<h4>Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "</h4>"; content += "<h4>Heating requested: " + String(datalayer_extended.nissanleaf.HeaterSendRequest) + "</h4>";
#endif #endif
#if !defined(TESLA_BATTERY) && !defined(NISSAN_LEAF_BATTERY) && \ #ifdef RENAULT_ZOE_GEN2_BATTERY
!defined(BMW_I3_BATTERY)&& !defined(BMW_IX_BATTERY) //Only the listed types have extra info 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) && \
!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

View file

@ -459,7 +459,7 @@ String processor(const String& var) {
"<h4>loop() task max load last 10 s: " + String(datalayer.system.status.loop_task_10s_max_us) + " us</h4>"; "<h4>loop() task max load last 10 s: " + String(datalayer.system.status.loop_task_10s_max_us) + " us</h4>";
content += "<h4>Max load @ worst case execution of core task:</h4>"; content += "<h4>Max load @ worst case execution of core task:</h4>";
content += "<h4>10ms function timing: " + String(datalayer.system.status.time_snap_10ms_us) + " us</h4>"; content += "<h4>10ms function timing: " + String(datalayer.system.status.time_snap_10ms_us) + " us</h4>";
content += "<h4>5s function timing: " + String(datalayer.system.status.time_snap_5s_us) + " us</h4>"; content += "<h4>Values function timing: " + String(datalayer.system.status.time_snap_values_us) + " us</h4>";
content += "<h4>CAN/serial RX function timing: " + String(datalayer.system.status.time_snap_comm_us) + " us</h4>"; content += "<h4>CAN/serial RX function timing: " + String(datalayer.system.status.time_snap_comm_us) + " us</h4>";
content += "<h4>CAN TX function timing: " + String(datalayer.system.status.time_snap_cantx_us) + " us</h4>"; content += "<h4>CAN TX function timing: " + String(datalayer.system.status.time_snap_cantx_us) + " us</h4>";
content += "<h4>OTA function timing: " + String(datalayer.system.status.time_snap_ota_us) + " us</h4>"; content += "<h4>OTA function timing: " + String(datalayer.system.status.time_snap_ota_us) + " us</h4>";
@ -495,6 +495,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
@ -519,6 +522,9 @@ String processor(const String& var) {
#ifdef BYD_ATTO_3_BATTERY #ifdef BYD_ATTO_3_BATTERY
content += "BYD Atto 3"; content += "BYD Atto 3";
#endif // BYD_ATTO_3_BATTERY #endif // BYD_ATTO_3_BATTERY
#ifdef CELLPOWER_BMS
content += "Cellpower BMS";
#endif // CELLPOWER_BMS
#ifdef CHADEMO_BATTERY #ifdef CHADEMO_BATTERY
content += "Chademo V2X mode"; content += "Chademo V2X mode";
#endif // CHADEMO_BATTERY #endif // CHADEMO_BATTERY
@ -719,6 +725,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;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Neg.: ";
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Pos.: ";
if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += "</h4>";
#endif #endif
// Close the block // Close the block
@ -761,7 +788,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>";
@ -803,6 +832,11 @@ String processor(const String& var) {
content += "<span style='color: red;'>&#10005;</span></h4>"; content += "<span style='color: red;'>&#10005;</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) {
@ -811,12 +845,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;'>&#10003;</span>";
content += "<h4 style='color: red;'>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>"; } else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Neg.: ";
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += " Cont. Pos.: ";
if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) {
content += "<span style='color: green;'>&#10003;</span>";
} else {
content += "<span style='color: red;'>&#10005;</span>";
}
content += "</h4>";
#endif
content += "</div>"; content += "</div>";
content += "</div>"; content += "</div>";

View file

@ -84,12 +84,14 @@ static uint16_t charge_current = 0;
static int16_t temperature_average = 0; static int16_t temperature_average = 0;
static uint16_t inverter_voltage = 0; static uint16_t inverter_voltage = 0;
static uint16_t inverter_SOC = 0; static uint16_t inverter_SOC = 0;
static uint16_t remaining_capacity_ah = 0;
static uint16_t fully_charged_capacity_ah = 0;
static long inverter_timestamp = 0; static long inverter_timestamp = 0;
static bool initialDataSent = 0; static bool initialDataSent = 0;
void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages void update_values_can_inverter() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//Calculate values
/* Calculate allowed charge/discharge currents*/
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0 if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
charge_current = charge_current =
((datalayer.battery.status.max_charge_power_W * 10) / ((datalayer.battery.status.max_charge_power_W * 10) /
@ -103,22 +105,30 @@ void update_values_can_inverter() { //This function maps all the values fetched
//The above calculation results in (30 000*10)/3700=81A //The above calculation results in (30 000*10)/3700=81A
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A) discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
} }
/* Restrict values from user settings if needed*/
if (charge_current > datalayer.battery.info.max_charge_amp_dA) { if (charge_current > datalayer.battery.info.max_charge_amp_dA) {
charge_current = charge_current =
datalayer.battery.info datalayer.battery.info
.max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values. .max_charge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
} }
if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) { if (discharge_current > datalayer.battery.info.max_discharge_amp_dA) {
discharge_current = discharge_current =
datalayer.battery.info datalayer.battery.info
.max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values. .max_discharge_amp_dA; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
} }
/* Calculate temperature */
temperature_average = temperature_average =
((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2); ((datalayer.battery.status.temperature_max_dC + datalayer.battery.status.temperature_min_dC) / 2);
/* Calculate capacity, Amp hours(Ah) = Watt hours (Wh) / Voltage (V)*/
if (datalayer.battery.status.voltage_dV > 10) { // Only update value when we have voltage available to avoid div0
remaining_capacity_ah =
((datalayer.battery.status.reported_remaining_capacity_Wh / datalayer.battery.status.voltage_dV) * 100);
fully_charged_capacity_ah =
((datalayer.battery.info.total_capacity_Wh / datalayer.battery.status.voltage_dV) * 100);
}
//Map values to CAN messages //Map values to CAN messages
//Maxvoltage (eg 400.0V = 4000 , 16bits long) //Maxvoltage (eg 400.0V = 4000 , 16bits long)
BYD_110.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8); BYD_110.data.u8[0] = (datalayer.battery.info.max_design_voltage_dV >> 8);
@ -139,12 +149,12 @@ void update_values_can_inverter() { //This function maps all the values fetched
//StateOfHealth (100.00%) //StateOfHealth (100.00%)
BYD_150.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8); BYD_150.data.u8[2] = (datalayer.battery.status.soh_pptt >> 8);
BYD_150.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF); BYD_150.data.u8[3] = (datalayer.battery.status.soh_pptt & 0x00FF);
//Maximum discharge power allowed (Unit: A+1) //Remaining capacity (Ah+1)
BYD_150.data.u8[4] = (discharge_current >> 8); BYD_150.data.u8[4] = (remaining_capacity_ah >> 8);
BYD_150.data.u8[5] = (discharge_current & 0x00FF); BYD_150.data.u8[5] = (remaining_capacity_ah & 0x00FF);
//Maximum charge power allowed (Unit: A+1) //Fully charged capacity (Ah+1)
BYD_150.data.u8[6] = (charge_current >> 8); BYD_150.data.u8[6] = (fully_charged_capacity_ah >> 8);
BYD_150.data.u8[7] = (charge_current & 0x00FF); BYD_150.data.u8[7] = (fully_charged_capacity_ah & 0x00FF);
//Voltage (ex 370.0) //Voltage (ex 370.0)
BYD_1D0.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8); BYD_1D0.data.u8[0] = (datalayer.battery.status.voltage_dV >> 8);

View file

@ -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

View 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

View 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

View file

@ -256,7 +256,9 @@ void ModbusServerRTU::serve(ModbusServerRTU *myServer) {
if (request[0] != TIMEOUT) { if (request[0] != TIMEOUT) {
// Any other error could be important for debugging, so print it // Any other error could be important for debugging, so print it
ModbusError me((Error)request[0]); ModbusError me((Error)request[0]);
#ifdef DEBUG_VIA_USB
LOG_E("RTU receive: %02X - %s\n", (int)me, (const char *)me); LOG_E("RTU receive: %02X - %s\n", (int)me, (const char *)me);
#endif //DEBUG_VIA_USB
} }
} }
// Give scheduler room to breathe // Give scheduler room to breathe