mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 19:42:08 +02:00
Merge branch 'main' into feature/bmw-ix-support
This commit is contained in:
commit
a52d6a3233
31 changed files with 1745 additions and 433 deletions
1
.github/workflows/compile-all-batteries.yml
vendored
1
.github/workflows/compile-all-batteries.yml
vendored
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
349
Software/src/battery/CELLPOWER-BMS.cpp
Normal file
349
Software/src/battery/CELLPOWER-BMS.cpp
Normal 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
|
19
Software/src/battery/CELLPOWER-BMS.h
Normal file
19
Software/src/battery/CELLPOWER-BMS.h
Normal 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
|
|
@ -6,13 +6,7 @@
|
||||||
#include "KIA-E-GMP-BATTERY.h"
|
#include "KIA-E-GMP-BATTERY.h"
|
||||||
|
|
||||||
/* Do not change code below unless you are sure what you are doing */
|
/* Do not change code below unless you are sure what you are doing */
|
||||||
static unsigned long previousMillis10ms = 0; // will store last time a 10ms CAN Message was send
|
|
||||||
static unsigned long previousMillis20ms = 0; // will store last time a 20ms CAN Message was send
|
|
||||||
static unsigned long previousMillis30ms = 0; // will store last time a 30ms CAN Message was send
|
|
||||||
static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send
|
|
||||||
static unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send
|
static unsigned long previousMillis200ms = 0; // will store last time a 200ms CAN Message was send
|
||||||
static unsigned long previousMillis500ms = 0; // will store last time a 500ms CAN Message was send
|
|
||||||
static unsigned long previousMillis1s = 0; // will store last time a 1s CAN Message was send
|
|
||||||
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
|
static unsigned long previousMillis10s = 0; // will store last time a 10s CAN Message was send
|
||||||
|
|
||||||
const unsigned char crc8_table[256] =
|
const unsigned char crc8_table[256] =
|
||||||
|
@ -63,7 +57,7 @@ static uint8_t KIA_7E4_COUNTER = 0x01;
|
||||||
static int8_t temperature_water_inlet = 0;
|
static int8_t temperature_water_inlet = 0;
|
||||||
static int8_t powerRelayTemperature = 0;
|
static int8_t powerRelayTemperature = 0;
|
||||||
static int8_t heatertemp = 0;
|
static int8_t heatertemp = 0;
|
||||||
|
static bool set_voltage_limits = false;
|
||||||
static uint8_t ticks_200ms_counter = 0;
|
static uint8_t ticks_200ms_counter = 0;
|
||||||
static uint8_t EGMP_1CF_counter = 0;
|
static uint8_t EGMP_1CF_counter = 0;
|
||||||
static uint8_t EGMP_3XF_counter = 0;
|
static uint8_t EGMP_3XF_counter = 0;
|
||||||
|
@ -582,208 +576,7 @@ CAN_frame* messages[] = {&message_1, &message_2, &message_3, &message_4, &me
|
||||||
&message_43, &message_44, &message_45, &message_46, &message_47, &message_48, &message_49,
|
&message_43, &message_44, &message_45, &message_46, &message_47, &message_48, &message_49,
|
||||||
&message_50, &message_51, &message_52, &message_53, &message_54, &message_55, &message_56,
|
&message_50, &message_51, &message_52, &message_53, &message_54, &message_55, &message_56,
|
||||||
&message_57, &message_58, &message_59, &message_60, &message_61, &message_62, &message_63};
|
&message_57, &message_58, &message_59, &message_60, &message_61, &message_62, &message_63};
|
||||||
|
/* PID polling messages */
|
||||||
/* These messages are rest of the vehicle messages, to reduce number of active fault codes */
|
|
||||||
CAN_frame EGMP_1CF = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x1CF,
|
|
||||||
.data = {0x56, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_3AA = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3AA,
|
|
||||||
.data = {0xFF, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_3E0 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3E0,
|
|
||||||
.data = {0xC3, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_3E1 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3E1,
|
|
||||||
.data = {0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_36F = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x36F,
|
|
||||||
.data = {0x28, 0x31, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_37F = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x37F,
|
|
||||||
.data = {0x9B, 0x30, 0x52, 0x24, 0x41, 0x02, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4B4 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4B4,
|
|
||||||
.data = {0x00, 0x00, 0xC0, 0x3F, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4B5 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4B5,
|
|
||||||
.data = {0x08, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4B7 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4B7,
|
|
||||||
.data = {0x08, 0x00, 0xF0, 0x07, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4CC = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4CC,
|
|
||||||
.data = {0x08, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4CE = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4CE,
|
|
||||||
.data = {0x16, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}};
|
|
||||||
CAN_frame EGMP_4D8 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4D8,
|
|
||||||
.data = {0x40, 0x10, 0xF0, 0xF0, 0x40, 0xF2, 0x1E, 0xCC}};
|
|
||||||
CAN_frame EGMP_4DD = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4DD,
|
|
||||||
.data = {0x3F, 0xFC, 0xFF, 0x00, 0x38, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4E7 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4E7,
|
|
||||||
.data = {0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00}};
|
|
||||||
CAN_frame EGMP_4E9 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4E9,
|
|
||||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4EA = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4EA,
|
|
||||||
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4EB = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4EB,
|
|
||||||
.data = {0x01, 0x50, 0x0B, 0x26, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4EC = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4EC,
|
|
||||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F}};
|
|
||||||
CAN_frame EGMP_4ED = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4ED,
|
|
||||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F}};
|
|
||||||
CAN_frame EGMP_4EE = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4EE,
|
|
||||||
.data = {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4EF = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4EF,
|
|
||||||
.data = {0x2B, 0xFE, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_405 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x405,
|
|
||||||
.data = {0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_410 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x410,
|
|
||||||
.data = {0xA6, 0x10, 0xFF, 0x3C, 0xFF, 0x7F, 0xFF, 0xFF}};
|
|
||||||
CAN_frame EGMP_411 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x411,
|
|
||||||
.data = {0xEA, 0x22, 0x50, 0x51, 0x00, 0x00, 0x00, 0x40}};
|
|
||||||
CAN_frame EGMP_412 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x412,
|
|
||||||
.data = {0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_413 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x413,
|
|
||||||
.data = {0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_414 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x414,
|
|
||||||
.data = {0xF0, 0x10, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_416 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x416,
|
|
||||||
.data = {0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_417 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x417,
|
|
||||||
.data = {0xC7, 0x10, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_418 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x418,
|
|
||||||
.data = {0x17, 0x20, 0x00, 0x00, 0x14, 0x0C, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_3C1 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3C1,
|
|
||||||
.data = {0x59, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_3C2 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x3C2,
|
|
||||||
.data = {0x07, 0x00, 0x11, 0x40, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_4F0 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4F0,
|
|
||||||
.data = {0x8A, 0x0A, 0x0D, 0x34, 0x60, 0x18, 0x12, 0xFC}};
|
|
||||||
CAN_frame EGMP_4F2 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4F2,
|
|
||||||
.data = {0x0A, 0xC3, 0xD5, 0xFF, 0x0F, 0x21, 0x80, 0x2B}};
|
|
||||||
CAN_frame EGMP_4FE = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x4FE,
|
|
||||||
.data = {0x69, 0x3F, 0x00, 0x04, 0xDF, 0x01, 0x4C, 0xA8}};
|
|
||||||
CAN_frame EGMP_48F = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x48F,
|
|
||||||
.data = {0xAD, 0x10, 0x41, 0x00, 0x05, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_419 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x419,
|
|
||||||
.data = {0xC7, 0x90, 0xB9, 0xD2, 0x0D, 0x62, 0x7A, 0x00}};
|
|
||||||
CAN_frame EGMP_422 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x422,
|
|
||||||
.data = {0x15, 0x10, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_444 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x444,
|
|
||||||
.data = {0x96, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
||||||
CAN_frame EGMP_641 = {.FD = true,
|
|
||||||
.ext_ID = false,
|
|
||||||
.DLC = 8,
|
|
||||||
.ID = 0x641,
|
|
||||||
.data = {0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF}};
|
|
||||||
CAN_frame EGMP_7E4 = {.FD = true,
|
CAN_frame EGMP_7E4 = {.FD = true,
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
|
@ -804,6 +597,32 @@ void set_cell_voltages(CAN_frame rx_frame, int start, int length, int startCell)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_voltage_minmax_limits() {
|
||||||
|
|
||||||
|
uint8_t valid_cell_count = 0;
|
||||||
|
for (int i = 0; i < MAX_AMOUNT_CELLS; ++i) {
|
||||||
|
if (datalayer.battery.status.cell_voltages_mV[i] > 0) {
|
||||||
|
++valid_cell_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (valid_cell_count == 144) {
|
||||||
|
datalayer.battery.info.number_of_cells = valid_cell_count;
|
||||||
|
datalayer.battery.info.max_design_voltage_dV = 6048;
|
||||||
|
datalayer.battery.info.min_design_voltage_dV = 4320;
|
||||||
|
} else if (valid_cell_count == 180) {
|
||||||
|
datalayer.battery.info.number_of_cells = valid_cell_count;
|
||||||
|
datalayer.battery.info.max_design_voltage_dV = 7560;
|
||||||
|
datalayer.battery.info.min_design_voltage_dV = 5400;
|
||||||
|
} else if (valid_cell_count == 192) {
|
||||||
|
datalayer.battery.info.number_of_cells = valid_cell_count;
|
||||||
|
datalayer.battery.info.max_design_voltage_dV = 8064;
|
||||||
|
datalayer.battery.info.min_design_voltage_dV = 5760;
|
||||||
|
} else {
|
||||||
|
// We are still starting up? Not all cells available.
|
||||||
|
set_voltage_limits = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
|
static uint8_t calculateCRC(CAN_frame rx_frame, uint8_t length, uint8_t initial_value) {
|
||||||
uint8_t crc = initial_value;
|
uint8_t crc = initial_value;
|
||||||
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
|
for (uint8_t j = 1; j < length; j++) { //start at 1, since 0 is the CRC
|
||||||
|
@ -826,8 +645,16 @@ void update_values_battery() { //This function maps all the values fetched via
|
||||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
//datalayer.battery.status.max_charge_power_W = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts
|
//datalayer.battery.status.max_charge_power_W = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts
|
||||||
//The allowed charge power is not available. We hardcode this value for now
|
//The allowed charge power is not available. We estimate this value for now
|
||||||
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
if (datalayer.battery.status.real_soc > 9900) {
|
||||||
|
datalayer.battery.status.max_charge_power_W = 0;
|
||||||
|
} else if (datalayer.battery.status.real_soc >
|
||||||
|
RAMPDOWN_SOC) { // When real SOC is between 90-99%, ramp the value between Max<->0
|
||||||
|
datalayer.battery.status.max_charge_power_W =
|
||||||
|
RAMPDOWNPOWERALLOWED * (1 - (datalayer.battery.status.real_soc - RAMPDOWN_SOC) / (10000.0 - RAMPDOWN_SOC));
|
||||||
|
} else { // No limits, max charging power allowed
|
||||||
|
datalayer.battery.status.max_charge_power_W = MAXCHARGEPOWERALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
|
//datalayer.battery.status.max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
|
||||||
//The allowed discharge power is not available. We hardcode this value for now
|
//The allowed discharge power is not available. We hardcode this value for now
|
||||||
|
@ -845,6 +672,11 @@ void update_values_battery() { //This function maps all the values fetched via
|
||||||
|
|
||||||
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
|
datalayer.battery.status.cell_min_voltage_mV = CellVoltMin_mV;
|
||||||
|
|
||||||
|
if ((millis() > INTERVAL_60_S) && !set_voltage_limits) {
|
||||||
|
set_voltage_limits = true;
|
||||||
|
set_voltage_minmax_limits(); // Count cells, and set voltage limits accordingly
|
||||||
|
}
|
||||||
|
|
||||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||||
set_event(EVENT_CANFD_RX_FAILURE, 0);
|
set_event(EVENT_CANFD_RX_FAILURE, 0);
|
||||||
|
@ -1178,16 +1010,16 @@ void send_can_battery() {
|
||||||
messageIndex = 0;
|
messageIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Send 500ms CANFD message
|
//Send 200ms CANFD message
|
||||||
if (currentMillis - previousMillis500ms >= INTERVAL_500_MS) {
|
if (currentMillis - previousMillis200ms >= INTERVAL_200_MS) {
|
||||||
previousMillis500ms = currentMillis;
|
previousMillis200ms = currentMillis;
|
||||||
// Check if sending of CAN messages has been delayed too much.
|
// Check if sending of CAN messages has been delayed too much.
|
||||||
if ((currentMillis - previousMillis500ms >= INTERVAL_500_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
if ((currentMillis - previousMillis200ms >= INTERVAL_200_MS_DELAYED) && (currentMillis > BOOTUP_TIME)) {
|
||||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis500ms));
|
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200ms));
|
||||||
} else {
|
} else {
|
||||||
clear_event(EVENT_CAN_OVERRUN);
|
clear_event(EVENT_CAN_OVERRUN);
|
||||||
}
|
}
|
||||||
previousMillis500ms = currentMillis;
|
previousMillis200ms = currentMillis;
|
||||||
|
|
||||||
EGMP_7E4.data.u8[3] = KIA_7E4_COUNTER;
|
EGMP_7E4.data.u8[3] = KIA_7E4_COUNTER;
|
||||||
|
|
||||||
|
@ -1200,19 +1032,10 @@ void send_can_battery() {
|
||||||
KIA_7E4_COUNTER = 0x01;
|
KIA_7E4_COUNTER = 0x01;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Send 1s CANFD message
|
|
||||||
if (currentMillis - previousMillis1s >= INTERVAL_1_S) {
|
|
||||||
previousMillis1s = currentMillis;
|
|
||||||
/* COMMENTED OUT WHILE CONTACTOR CLOSING TESTING
|
|
||||||
transmit_can(&EGMP_48F, can_config.battery);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
//Send 10s CANFD message
|
//Send 10s CANFD message
|
||||||
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
if (currentMillis - previousMillis10s >= INTERVAL_10_S) {
|
||||||
previousMillis10s = currentMillis;
|
previousMillis10s = currentMillis;
|
||||||
/* COMMENTED OUT WHILE CONTACTOR CLOSING TESTING
|
|
||||||
transmit_can(&EGMP_4FE, can_config.battery);
|
|
||||||
*/
|
|
||||||
ok_start_polling_battery = true;
|
ok_start_polling_battery = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1232,6 +1055,7 @@ void setup_battery(void) { // Performs one time setup at startup
|
||||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
||||||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
||||||
|
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -14,6 +14,8 @@ extern ACAN2517FD canfd;
|
||||||
#define MIN_CELL_VOLTAGE_MV 2950 //Battery is put into emergency stop if one cell goes below this value
|
#define MIN_CELL_VOLTAGE_MV 2950 //Battery is put into emergency stop if one cell goes below this value
|
||||||
#define MAXCHARGEPOWERALLOWED 10000
|
#define MAXCHARGEPOWERALLOWED 10000
|
||||||
#define MAXDISCHARGEPOWERALLOWED 10000
|
#define MAXDISCHARGEPOWERALLOWED 10000
|
||||||
|
#define RAMPDOWN_SOC 9000 // 90.00 SOC% to start ramping down from max charge power towards 0 at 100.00%
|
||||||
|
#define RAMPDOWNPOWERALLOWED 10000 // What power we ramp down from towards top balancing
|
||||||
|
|
||||||
void setup_battery(void);
|
void setup_battery(void);
|
||||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||||
|
|
|
@ -67,6 +67,9 @@ void update_values_battery() {
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10));
|
datalayer.battery.status.max_discharge_power_W = (-max_discharge_current * (voltage_dV / 10));
|
||||||
|
|
||||||
|
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||||
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV;
|
datalayer.battery.status.cell_max_voltage_mV = cellvoltage_max_mV;
|
||||||
|
|
||||||
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV;
|
datalayer.battery.status.cell_min_voltage_mV = cellvoltage_min_mV;
|
||||||
|
|
|
@ -1,23 +1,69 @@
|
||||||
#include "../include.h"
|
#include "../include.h"
|
||||||
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
#ifdef RENAULT_ZOE_GEN2_BATTERY
|
||||||
#include "../datalayer/datalayer.h"
|
#include "../datalayer/datalayer.h"
|
||||||
|
#include "../datalayer/datalayer_extended.h" //For "More battery info" webpage
|
||||||
#include "../devboard/utils/events.h"
|
#include "../devboard/utils/events.h"
|
||||||
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
#include "RENAULT-ZOE-GEN2-BATTERY.h"
|
||||||
|
|
||||||
/* Information in this file is based of the OVMS V3 vehicle_renaultzoe.cpp component
|
/* TODO
|
||||||
|
- Add //NVROL Reset
|
||||||
|
- Add //Enable temporisation before sleep (see ljames28 repo)
|
||||||
|
|
||||||
|
"If the pack is in a state where it is confused about the time, you may need to reset it's NVROL memory.
|
||||||
|
However, if the power is later power cycled, it will revert back to his previous confused state.
|
||||||
|
Therefore, after resetting the NVROL you must enable "temporisation before sleep", and then stop streaming 373.
|
||||||
|
It will then save the data and go to sleep. When the pack is confused, the state of charge may reset back to incorrect value
|
||||||
|
every time the power is reset which can be dangerous. In this state, the voltage will still be accurate"
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Information in this file is based on:
|
||||||
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp
|
https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3/blob/master/vehicle/OVMS.V3/components/vehicle_renaultzoe_ph2_obd/src/vehicle_renaultzoe_ph2_obd.cpp
|
||||||
|
https://github.com/ljames28/Renault-Zoe-PH2-ZE50-Canbus-LBC-Information?tab=readme-ov-file
|
||||||
|
https://github.com/fesch/CanZE/tree/master/app/src/main/assets/ZOE_Ph2
|
||||||
/*
|
/*
|
||||||
|
|
||||||
/* Do not change code below unless you are sure what you are doing */
|
/* Do not change code below unless you are sure what you are doing */
|
||||||
static uint16_t LB_SOC = 50;
|
static uint16_t battery_soc = 0;
|
||||||
static uint16_t LB_SOH = 99;
|
static uint16_t battery_usable_soc = 5000;
|
||||||
static int16_t LB_Average_Temperature = 0;
|
static uint16_t battery_soh = 10000;
|
||||||
static uint32_t LB_Charge_Power_W = 0;
|
static uint16_t battery_pack_voltage = 370;
|
||||||
static int32_t LB_Current = 0;
|
static uint16_t battery_max_cell_voltage = 3700;
|
||||||
static uint16_t LB_kWh_Remaining = 0;
|
static uint16_t battery_min_cell_voltage = 3700;
|
||||||
static uint16_t LB_Cell_Max_Voltage = 3700;
|
static uint16_t battery_12v = 0;
|
||||||
static uint16_t LB_Cell_Min_Voltage = 3700;
|
static uint16_t battery_avg_temp = 920;
|
||||||
static uint16_t LB_Battery_Voltage = 3700;
|
static uint16_t battery_min_temp = 920;
|
||||||
|
static uint16_t battery_max_temp = 920;
|
||||||
|
static uint16_t battery_max_power = 0;
|
||||||
|
static uint16_t battery_interlock = 0;
|
||||||
|
static uint16_t battery_kwh = 0;
|
||||||
|
static int32_t battery_current = 32640;
|
||||||
|
static uint16_t battery_current_offset = 0;
|
||||||
|
static uint16_t battery_max_generated = 0;
|
||||||
|
static uint16_t battery_max_available = 0;
|
||||||
|
static uint16_t battery_current_voltage = 0;
|
||||||
|
static uint16_t battery_charging_status = 0;
|
||||||
|
static uint16_t battery_remaining_charge = 0;
|
||||||
|
static uint16_t battery_balance_capacity_total = 0;
|
||||||
|
static uint16_t battery_balance_time_total = 0;
|
||||||
|
static uint16_t battery_balance_capacity_sleep = 0;
|
||||||
|
static uint16_t battery_balance_time_sleep = 0;
|
||||||
|
static uint16_t battery_balance_capacity_wake = 0;
|
||||||
|
static uint16_t battery_balance_time_wake = 0;
|
||||||
|
static uint16_t battery_bms_state = 0;
|
||||||
|
static uint16_t battery_balance_switches = 0;
|
||||||
|
static uint16_t battery_energy_complete = 0;
|
||||||
|
static uint16_t battery_energy_partial = 0;
|
||||||
|
static uint16_t battery_slave_failures = 0;
|
||||||
|
static uint16_t battery_mileage = 0;
|
||||||
|
static uint16_t battery_fan_speed = 0;
|
||||||
|
static uint16_t battery_fan_period = 0;
|
||||||
|
static uint16_t battery_fan_control = 0;
|
||||||
|
static uint16_t battery_fan_duty = 0;
|
||||||
|
static uint16_t battery_temporisation = 0;
|
||||||
|
static uint16_t battery_time = 0;
|
||||||
|
static uint16_t battery_pack_time = 0;
|
||||||
|
static uint16_t battery_soc_min = 0;
|
||||||
|
static uint16_t battery_soc_max = 0;
|
||||||
|
|
||||||
CAN_frame ZOE_373 = {.FD = false,
|
CAN_frame ZOE_373 = {.FD = false,
|
||||||
.ext_ID = false,
|
.ext_ID = false,
|
||||||
|
@ -28,36 +74,151 @@ CAN_frame ZOE_POLL_18DADBF1 = {.FD = false,
|
||||||
.ext_ID = true,
|
.ext_ID = true,
|
||||||
.DLC = 8,
|
.DLC = 8,
|
||||||
.ID = 0x18DADBF1,
|
.ID = 0x18DADBF1,
|
||||||
.data = {0x03, 0x22, 0x90, 0x00, 0xff, 0xff, 0xff, 0xff}};
|
.data = {0x03, 0x22, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
|
//NVROL Reset
|
||||||
|
CAN_frame ZOE_NVROL_1_18DADBF1 = {.FD = false,
|
||||||
|
.ext_ID = true,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x18DADBF1,
|
||||||
|
.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
|
||||||
|
CAN_frame ZOE_NVROL_2_18DADBF1 = {.FD = false,
|
||||||
|
.ext_ID = true,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x18DADBF1,
|
||||||
|
.data = {0x04, 0x31, 0x01, 0xB0, 0x09, 0x00, 0xAA, 0xAA}};
|
||||||
|
//Enable temporisation before sleep
|
||||||
|
CAN_frame ZOE_SLEEP_1_18DADBF1 = {.FD = false,
|
||||||
|
.ext_ID = true,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x18DADBF1,
|
||||||
|
.data = {0x02, 0x10, 0x03, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
|
||||||
|
CAN_frame ZOE_SLEEP_2_18DADBF1 = {.FD = false,
|
||||||
|
.ext_ID = true,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x18DADBF1,
|
||||||
|
.data = {0x04, 0x2E, 0x92, 0x81, 0x01, 0xAA, 0xAA, 0xAA}};
|
||||||
|
|
||||||
|
const uint16_t poll_commands[41] = {POLL_SOC,
|
||||||
|
POLL_USABLE_SOC,
|
||||||
|
POLL_SOH,
|
||||||
|
POLL_PACK_VOLTAGE,
|
||||||
|
POLL_MAX_CELL_VOLTAGE,
|
||||||
|
POLL_MIN_CELL_VOLTAGE,
|
||||||
|
POLL_12V,
|
||||||
|
POLL_AVG_TEMP,
|
||||||
|
POLL_MIN_TEMP,
|
||||||
|
POLL_MAX_TEMP,
|
||||||
|
POLL_MAX_POWER,
|
||||||
|
POLL_INTERLOCK,
|
||||||
|
POLL_KWH,
|
||||||
|
POLL_CURRENT,
|
||||||
|
POLL_CURRENT_OFFSET,
|
||||||
|
POLL_MAX_GENERATED,
|
||||||
|
POLL_MAX_AVAILABLE,
|
||||||
|
POLL_CURRENT_VOLTAGE,
|
||||||
|
POLL_CHARGING_STATUS,
|
||||||
|
POLL_REMAINING_CHARGE,
|
||||||
|
POLL_BALANCE_CAPACITY_TOTAL,
|
||||||
|
POLL_BALANCE_TIME_TOTAL,
|
||||||
|
POLL_BALANCE_CAPACITY_SLEEP,
|
||||||
|
POLL_BALANCE_TIME_SLEEP,
|
||||||
|
POLL_BALANCE_CAPACITY_WAKE,
|
||||||
|
POLL_BALANCE_TIME_WAKE,
|
||||||
|
POLL_BMS_STATE,
|
||||||
|
POLL_BALANCE_SWITCHES,
|
||||||
|
POLL_ENERGY_COMPLETE,
|
||||||
|
POLL_ENERGY_PARTIAL,
|
||||||
|
POLL_SLAVE_FAILURES,
|
||||||
|
POLL_MILEAGE,
|
||||||
|
POLL_FAN_SPEED,
|
||||||
|
POLL_FAN_PERIOD,
|
||||||
|
POLL_FAN_CONTROL,
|
||||||
|
POLL_FAN_DUTY,
|
||||||
|
POLL_TEMPORISATION,
|
||||||
|
POLL_TIME,
|
||||||
|
POLL_PACK_TIME,
|
||||||
|
POLL_SOC_MIN,
|
||||||
|
POLL_SOC_MAX};
|
||||||
|
static uint8_t poll_index = 0;
|
||||||
|
static uint16_t currentpoll = POLL_SOC;
|
||||||
|
static uint16_t reply_poll = 0;
|
||||||
|
|
||||||
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
|
static unsigned long previousMillis200 = 0; // will store last time a 200ms CAN Message was sent
|
||||||
|
|
||||||
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
|
||||||
datalayer.battery.status.soh_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
|
datalayer.battery.status.soh_pptt = battery_soh;
|
||||||
|
|
||||||
datalayer.battery.status.real_soc = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
|
if (battery_soc >= 300) {
|
||||||
|
datalayer.battery.status.real_soc = battery_soc - 300;
|
||||||
|
} else {
|
||||||
|
datalayer.battery.status.real_soc = 0;
|
||||||
|
}
|
||||||
|
|
||||||
datalayer.battery.status.voltage_dV = LB_Battery_Voltage;
|
datalayer.battery.status.voltage_dV = battery_pack_voltage;
|
||||||
|
|
||||||
datalayer.battery.status.current_dA = LB_Current;
|
datalayer.battery.status.current_dA = ((battery_current - 32640) * 0.3125);
|
||||||
|
|
||||||
//Calculate the remaining Wh amount from SOC% and max Wh value.
|
//Calculate the remaining Wh amount from SOC% and max Wh value.
|
||||||
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
datalayer.battery.status.remaining_capacity_Wh = static_cast<uint32_t>(
|
||||||
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
(static_cast<double>(datalayer.battery.status.real_soc) / 10000) * datalayer.battery.info.total_capacity_Wh);
|
||||||
|
|
||||||
datalayer.battery.status.max_discharge_power_W;
|
datalayer.battery.status.max_discharge_power_W = battery_max_available * 10;
|
||||||
|
|
||||||
datalayer.battery.status.max_charge_power_W;
|
datalayer.battery.status.max_charge_power_W = battery_max_generated * 10;
|
||||||
|
|
||||||
datalayer.battery.status.active_power_W;
|
datalayer.battery.status.active_power_W =
|
||||||
|
(datalayer.battery.status.current_dA * (datalayer.battery.status.voltage_dV / 100));
|
||||||
|
|
||||||
datalayer.battery.status.temperature_min_dC;
|
datalayer.battery.status.temperature_min_dC = ((battery_min_temp - 640) * 0.625);
|
||||||
|
|
||||||
datalayer.battery.status.temperature_max_dC;
|
datalayer.battery.status.temperature_max_dC = ((battery_max_temp - 640) * 0.625);
|
||||||
|
|
||||||
datalayer.battery.status.cell_min_voltage_mV;
|
datalayer.battery.status.cell_min_voltage_mV = (battery_min_cell_voltage * 0.976563);
|
||||||
|
|
||||||
datalayer.battery.status.cell_max_voltage_mV;
|
datalayer.battery.status.cell_max_voltage_mV = (battery_max_cell_voltage * 0.976563);
|
||||||
|
|
||||||
|
// Update webserver datalayer
|
||||||
|
datalayer_extended.zoePH2.battery_soc = battery_soc;
|
||||||
|
datalayer_extended.zoePH2.battery_usable_soc = battery_usable_soc;
|
||||||
|
datalayer_extended.zoePH2.battery_soh = battery_soh;
|
||||||
|
datalayer_extended.zoePH2.battery_pack_voltage = battery_pack_voltage;
|
||||||
|
datalayer_extended.zoePH2.battery_max_cell_voltage = battery_max_cell_voltage;
|
||||||
|
datalayer_extended.zoePH2.battery_min_cell_voltage = battery_min_cell_voltage;
|
||||||
|
datalayer_extended.zoePH2.battery_12v = battery_12v;
|
||||||
|
datalayer_extended.zoePH2.battery_avg_temp = battery_avg_temp;
|
||||||
|
datalayer_extended.zoePH2.battery_min_temp = battery_min_temp;
|
||||||
|
datalayer_extended.zoePH2.battery_max_temp = battery_max_temp;
|
||||||
|
datalayer_extended.zoePH2.battery_max_power = battery_max_power;
|
||||||
|
datalayer_extended.zoePH2.battery_interlock = battery_interlock;
|
||||||
|
datalayer_extended.zoePH2.battery_kwh = battery_kwh;
|
||||||
|
datalayer_extended.zoePH2.battery_current = battery_current;
|
||||||
|
datalayer_extended.zoePH2.battery_current_offset = battery_current_offset;
|
||||||
|
datalayer_extended.zoePH2.battery_max_generated = battery_max_generated;
|
||||||
|
datalayer_extended.zoePH2.battery_max_available = battery_max_available;
|
||||||
|
datalayer_extended.zoePH2.battery_current_voltage = battery_current_voltage;
|
||||||
|
datalayer_extended.zoePH2.battery_charging_status = battery_charging_status;
|
||||||
|
datalayer_extended.zoePH2.battery_remaining_charge = battery_remaining_charge;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_capacity_total = battery_balance_capacity_total;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_time_total = battery_balance_time_total;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_capacity_sleep = battery_balance_capacity_sleep;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_time_sleep = battery_balance_time_sleep;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_capacity_wake = battery_balance_capacity_wake;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_time_wake = battery_balance_time_wake;
|
||||||
|
datalayer_extended.zoePH2.battery_bms_state = battery_bms_state;
|
||||||
|
datalayer_extended.zoePH2.battery_balance_switches = battery_balance_switches;
|
||||||
|
datalayer_extended.zoePH2.battery_energy_complete = battery_energy_complete;
|
||||||
|
datalayer_extended.zoePH2.battery_energy_partial = battery_energy_partial;
|
||||||
|
datalayer_extended.zoePH2.battery_slave_failures = battery_slave_failures;
|
||||||
|
datalayer_extended.zoePH2.battery_mileage = battery_mileage;
|
||||||
|
datalayer_extended.zoePH2.battery_fan_speed = battery_fan_speed;
|
||||||
|
datalayer_extended.zoePH2.battery_fan_period = battery_fan_period;
|
||||||
|
datalayer_extended.zoePH2.battery_fan_control = battery_fan_control;
|
||||||
|
datalayer_extended.zoePH2.battery_fan_duty = battery_fan_duty;
|
||||||
|
datalayer_extended.zoePH2.battery_temporisation = battery_temporisation;
|
||||||
|
datalayer_extended.zoePH2.battery_time = battery_time;
|
||||||
|
datalayer_extended.zoePH2.battery_pack_time = battery_pack_time;
|
||||||
|
datalayer_extended.zoePH2.battery_soc_min = battery_soc_min;
|
||||||
|
datalayer_extended.zoePH2.battery_soc_max = battery_soc_max;
|
||||||
|
|
||||||
#ifdef DEBUG_VIA_USB
|
#ifdef DEBUG_VIA_USB
|
||||||
|
|
||||||
|
@ -67,7 +228,137 @@ void update_values_battery() { //This function maps all the values fetched via
|
||||||
void receive_can_battery(CAN_frame rx_frame) {
|
void receive_can_battery(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
switch (rx_frame.ID) {
|
switch (rx_frame.ID) {
|
||||||
case 0x18daf1db: // LBC Reply from active polling
|
case 0x18DAF1DB: // LBC Reply from active polling
|
||||||
|
//frame 2 & 3 contains
|
||||||
|
reply_poll = (rx_frame.data.u8[2] << 8) | rx_frame.data.u8[3];
|
||||||
|
|
||||||
|
switch (reply_poll) {
|
||||||
|
case POLL_SOC:
|
||||||
|
battery_soc = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_USABLE_SOC:
|
||||||
|
battery_usable_soc = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_SOH:
|
||||||
|
battery_soh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_PACK_VOLTAGE:
|
||||||
|
battery_pack_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MAX_CELL_VOLTAGE:
|
||||||
|
battery_max_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MIN_CELL_VOLTAGE:
|
||||||
|
battery_min_cell_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_12V:
|
||||||
|
battery_12v = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_AVG_TEMP:
|
||||||
|
battery_avg_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MIN_TEMP:
|
||||||
|
battery_min_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MAX_TEMP:
|
||||||
|
battery_max_temp = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MAX_POWER:
|
||||||
|
battery_max_power = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_INTERLOCK:
|
||||||
|
battery_interlock = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_KWH:
|
||||||
|
battery_kwh = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_CURRENT:
|
||||||
|
battery_current = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_CURRENT_OFFSET:
|
||||||
|
battery_current_offset = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MAX_GENERATED:
|
||||||
|
battery_max_generated = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MAX_AVAILABLE:
|
||||||
|
battery_max_available = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_CURRENT_VOLTAGE:
|
||||||
|
battery_current_voltage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_CHARGING_STATUS:
|
||||||
|
battery_charging_status = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_REMAINING_CHARGE:
|
||||||
|
battery_remaining_charge = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_CAPACITY_TOTAL:
|
||||||
|
battery_balance_capacity_total = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_TIME_TOTAL:
|
||||||
|
battery_balance_time_total = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_CAPACITY_SLEEP:
|
||||||
|
battery_balance_capacity_sleep = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_TIME_SLEEP:
|
||||||
|
battery_balance_time_sleep = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_CAPACITY_WAKE:
|
||||||
|
battery_balance_capacity_wake = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_TIME_WAKE:
|
||||||
|
battery_balance_time_wake = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BMS_STATE:
|
||||||
|
battery_bms_state = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_BALANCE_SWITCHES:
|
||||||
|
battery_balance_switches = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_ENERGY_COMPLETE:
|
||||||
|
battery_energy_complete = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_ENERGY_PARTIAL:
|
||||||
|
battery_energy_partial = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_SLAVE_FAILURES:
|
||||||
|
battery_slave_failures = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_MILEAGE:
|
||||||
|
battery_mileage = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_FAN_SPEED:
|
||||||
|
battery_fan_speed = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_FAN_PERIOD:
|
||||||
|
battery_fan_period = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_FAN_CONTROL:
|
||||||
|
battery_fan_control = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_FAN_DUTY:
|
||||||
|
battery_fan_duty = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_TEMPORISATION:
|
||||||
|
battery_temporisation = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_TIME:
|
||||||
|
battery_time = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_PACK_TIME:
|
||||||
|
battery_pack_time = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_SOC_MIN:
|
||||||
|
battery_soc_min = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
case POLL_SOC_MAX:
|
||||||
|
battery_soc_max = (rx_frame.data.u8[4] << 8) | rx_frame.data.u8[5];
|
||||||
|
break;
|
||||||
|
default: // Unknown reply
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -83,8 +374,16 @@ void send_can_battery() {
|
||||||
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200));
|
set_event(EVENT_CAN_OVERRUN, (currentMillis - previousMillis200));
|
||||||
}
|
}
|
||||||
previousMillis200 = currentMillis;
|
previousMillis200 = currentMillis;
|
||||||
transmit_can(&ZOE_373, can_config.battery);
|
|
||||||
|
// Update current poll from the array
|
||||||
|
currentpoll = poll_commands[poll_index];
|
||||||
|
poll_index = (poll_index + 1) % 41;
|
||||||
|
|
||||||
|
ZOE_POLL_18DADBF1.data.u8[2] = (uint8_t)((currentpoll & 0xFF00) >> 8);
|
||||||
|
ZOE_POLL_18DADBF1.data.u8[3] = (uint8_t)(currentpoll & 0x00FF);
|
||||||
|
|
||||||
transmit_can(&ZOE_POLL_18DADBF1, can_config.battery);
|
transmit_can(&ZOE_POLL_18DADBF1, can_config.battery);
|
||||||
|
transmit_can(&ZOE_373, can_config.battery);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,4 +12,46 @@
|
||||||
void setup_battery(void);
|
void setup_battery(void);
|
||||||
void transmit_can(CAN_frame* tx_frame, int interface);
|
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||||
|
|
||||||
|
#define POLL_SOC 0x9001
|
||||||
|
#define POLL_USABLE_SOC 0x9002
|
||||||
|
#define POLL_SOH 0x9003
|
||||||
|
#define POLL_PACK_VOLTAGE 0x9005
|
||||||
|
#define POLL_MAX_CELL_VOLTAGE 0x9007
|
||||||
|
#define POLL_MIN_CELL_VOLTAGE 0x9009
|
||||||
|
#define POLL_12V 0x9011
|
||||||
|
#define POLL_AVG_TEMP 0x9012
|
||||||
|
#define POLL_MIN_TEMP 0x9013
|
||||||
|
#define POLL_MAX_TEMP 0x9014
|
||||||
|
#define POLL_MAX_POWER 0x9018
|
||||||
|
#define POLL_INTERLOCK 0x901A
|
||||||
|
#define POLL_KWH 0x91C8
|
||||||
|
#define POLL_CURRENT 0x925D
|
||||||
|
#define POLL_CURRENT_OFFSET 0x900C
|
||||||
|
#define POLL_MAX_GENERATED 0x900E
|
||||||
|
#define POLL_MAX_AVAILABLE 0x900F
|
||||||
|
#define POLL_CURRENT_VOLTAGE 0x9130
|
||||||
|
#define POLL_CHARGING_STATUS 0x9019
|
||||||
|
#define POLL_REMAINING_CHARGE 0xF45B
|
||||||
|
#define POLL_BALANCE_CAPACITY_TOTAL 0x924F
|
||||||
|
#define POLL_BALANCE_TIME_TOTAL 0x9250
|
||||||
|
#define POLL_BALANCE_CAPACITY_SLEEP 0x9251
|
||||||
|
#define POLL_BALANCE_TIME_SLEEP 0x9252
|
||||||
|
#define POLL_BALANCE_CAPACITY_WAKE 0x9262
|
||||||
|
#define POLL_BALANCE_TIME_WAKE 0x9263
|
||||||
|
#define POLL_BMS_STATE 0x9259
|
||||||
|
#define POLL_BALANCE_SWITCHES 0x912B
|
||||||
|
#define POLL_ENERGY_COMPLETE 0x9210
|
||||||
|
#define POLL_ENERGY_PARTIAL 0x9215
|
||||||
|
#define POLL_SLAVE_FAILURES 0x9129
|
||||||
|
#define POLL_MILEAGE 0x91CF
|
||||||
|
#define POLL_FAN_SPEED 0x912E
|
||||||
|
#define POLL_FAN_PERIOD 0x91F4
|
||||||
|
#define POLL_FAN_CONTROL 0x91C9
|
||||||
|
#define POLL_FAN_DUTY 0x91F5
|
||||||
|
#define POLL_TEMPORISATION 0x9281
|
||||||
|
#define POLL_TIME 0x9261
|
||||||
|
#define POLL_PACK_TIME 0x91C1
|
||||||
|
#define POLL_SOC_MIN 0x91B9
|
||||||
|
#define POLL_SOC_MAX 0x91BA
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -22,6 +22,8 @@ void print_units(char* header, int value, char* units) {
|
||||||
|
|
||||||
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
|
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
|
||||||
|
|
||||||
|
datalayer.battery.info.number_of_cells = 96;
|
||||||
|
|
||||||
datalayer.battery.status.real_soc = 5000; // 50.00%
|
datalayer.battery.status.real_soc = 5000; // 50.00%
|
||||||
|
|
||||||
datalayer.battery.status.soh_pptt = 9900; // 99.00%
|
datalayer.battery.status.soh_pptt = 9900; // 99.00%
|
||||||
|
@ -49,7 +51,7 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
||||||
datalayer.battery.status.max_charge_power_W = 5000; // 5kW
|
datalayer.battery.status.max_charge_power_W = 5000; // 5kW
|
||||||
|
|
||||||
for (int i = 0; i < 97; ++i) {
|
for (int i = 0; i < 97; ++i) {
|
||||||
datalayer.battery.status.cell_voltages_mV[i] = 3500 + i;
|
datalayer.battery.status.cell_voltages_mV[i] = 3700 + random(-20, 21);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Fake that we get CAN messages
|
//Fake that we get CAN messages
|
||||||
|
@ -71,6 +73,78 @@ void update_values_battery() { /* This function puts fake values onto the parame
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DOUBLE_BATTERY
|
||||||
|
|
||||||
|
void update_values_battery2() { // Handle the values coming in from battery #2
|
||||||
|
|
||||||
|
datalayer.battery2.info.number_of_cells = 96;
|
||||||
|
|
||||||
|
datalayer.battery2.status.real_soc = 5000; // 50.00%
|
||||||
|
|
||||||
|
datalayer.battery2.status.soh_pptt = 9900; // 99.00%
|
||||||
|
|
||||||
|
//datalayer.battery.status.voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
|
||||||
|
|
||||||
|
datalayer.battery2.status.current_dA = 0; // 0 A
|
||||||
|
|
||||||
|
datalayer.battery2.info.total_capacity_Wh = 30000; // 30kWh
|
||||||
|
|
||||||
|
datalayer.battery2.status.remaining_capacity_Wh = 15000; // 15kWh
|
||||||
|
|
||||||
|
datalayer.battery2.status.cell_max_voltage_mV = 3596;
|
||||||
|
|
||||||
|
datalayer.battery2.status.cell_min_voltage_mV = 3500;
|
||||||
|
|
||||||
|
datalayer.battery2.status.active_power_W = 0; // 0W
|
||||||
|
|
||||||
|
datalayer.battery2.status.temperature_min_dC = 50; // 5.0*C
|
||||||
|
|
||||||
|
datalayer.battery2.status.temperature_max_dC = 60; // 6.0*C
|
||||||
|
|
||||||
|
datalayer.battery2.status.max_discharge_power_W = 5000; // 5kW
|
||||||
|
|
||||||
|
datalayer.battery2.status.max_charge_power_W = 5000; // 5kW
|
||||||
|
|
||||||
|
for (int i = 0; i < 97; ++i) {
|
||||||
|
datalayer.battery2.status.cell_voltages_mV[i] = 3700 + random(-20, 21);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fake that we get CAN messages
|
||||||
|
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
|
||||||
|
/*Finally print out values to serial if configured to do so*/
|
||||||
|
#ifdef DEBUG_VIA_USB
|
||||||
|
Serial.println("FAKE Values battery 2 going to inverter");
|
||||||
|
print_units("SOH 2 %: ", (datalayer.battery2.status.soh_pptt * 0.01), "% ");
|
||||||
|
print_units(", SOC 2 %: ", (datalayer.battery2.status.reported_soc * 0.01), "% ");
|
||||||
|
print_units(", Voltage 2: ", (datalayer.battery2.status.voltage_dV * 0.1), "V ");
|
||||||
|
print_units(", Max discharge power 2: ", datalayer.battery2.status.max_discharge_power_W, "W ");
|
||||||
|
print_units(", Max charge power 2: ", datalayer.battery2.status.max_charge_power_W, "W ");
|
||||||
|
print_units(", Max temp 2: ", (datalayer.battery2.status.temperature_max_dC * 0.1), "°C ");
|
||||||
|
print_units(", Min temp 2: ", (datalayer.battery2.status.temperature_min_dC * 0.1), "°C ");
|
||||||
|
print_units(", Max cell voltage 2: ", datalayer.battery2.status.cell_max_voltage_mV, "mV ");
|
||||||
|
print_units(", Min cell voltage 2: ", datalayer.battery2.status.cell_min_voltage_mV, "mV ");
|
||||||
|
Serial.println("");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void receive_can_battery2(CAN_frame rx_frame) {
|
||||||
|
datalayer.battery2.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
|
// All CAN messages recieved will be logged via serial
|
||||||
|
Serial.print(millis()); // Example printout, time, ID, length, data: 7553 1DB 8 FF C0 B9 EA 0 0 2 5D
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.print(rx_frame.ID, HEX);
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.print(rx_frame.DLC);
|
||||||
|
Serial.print(" ");
|
||||||
|
for (int i = 0; i < rx_frame.DLC; ++i) {
|
||||||
|
Serial.print(rx_frame.data.u8[i], HEX);
|
||||||
|
Serial.print(" ");
|
||||||
|
}
|
||||||
|
Serial.println("");
|
||||||
|
}
|
||||||
|
#endif // DOUBLE_BATTERY
|
||||||
|
|
||||||
void receive_can_battery(CAN_frame rx_frame) {
|
void receive_can_battery(CAN_frame rx_frame) {
|
||||||
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
datalayer.battery.status.CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||||
// All CAN messages recieved will be logged via serial
|
// All CAN messages recieved will be logged via serial
|
||||||
|
@ -97,6 +171,8 @@ void send_can_battery() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup_battery(void) { // Performs one time setup at startup
|
void setup_battery(void) { // Performs one time setup at startup
|
||||||
|
randomSeed(analogRead(0));
|
||||||
|
|
||||||
#ifdef DEBUG_VIA_USB
|
#ifdef DEBUG_VIA_USB
|
||||||
Serial.println("Test mode with fake battery selected");
|
Serial.println("Test mode with fake battery selected");
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -150,6 +150,7 @@ void init_events(void) {
|
||||||
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
|
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
|
||||||
|
events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING;
|
||||||
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
|
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
|
||||||
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_CHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||||
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
events.entries[EVENT_DISCHARGE_LIMIT_EXCEEDED].level = EVENT_LEVEL_INFO;
|
||||||
|
@ -281,6 +282,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
||||||
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
|
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
|
||||||
case EVENT_CAN_INVERTER_MISSING:
|
case EVENT_CAN_INVERTER_MISSING:
|
||||||
return "Warning: Inverter not sending messages on CAN bus. Check wiring!";
|
return "Warning: Inverter not sending messages on CAN bus. Check wiring!";
|
||||||
|
case EVENT_CONTACTOR_WELDED:
|
||||||
|
return "Warning: Contactors sticking/welded. Inspect battery with caution!";
|
||||||
case EVENT_CHARGE_LIMIT_EXCEEDED:
|
case EVENT_CHARGE_LIMIT_EXCEEDED:
|
||||||
return "Info: Inverter is charging faster than battery is allowing.";
|
return "Info: Inverter is charging faster than battery is allowing.";
|
||||||
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
|
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
||||||
|
|
||||||
#define EE_MAGIC_HEADER_VALUE 0x0016 // 0x0000 to 0xFFFF
|
#define EE_MAGIC_HEADER_VALUE 0x0017 // 0x0000 to 0xFFFF
|
||||||
|
|
||||||
#define GENERATE_ENUM(ENUM) ENUM,
|
#define GENERATE_ENUM(ENUM) ENUM,
|
||||||
#define GENERATE_STRING(STRING) #STRING,
|
#define GENERATE_STRING(STRING) #STRING,
|
||||||
|
@ -37,6 +37,7 @@
|
||||||
XX(EVENT_CAN_TX_FAILURE) \
|
XX(EVENT_CAN_TX_FAILURE) \
|
||||||
XX(EVENT_CAN_INVERTER_MISSING) \
|
XX(EVENT_CAN_INVERTER_MISSING) \
|
||||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||||
|
XX(EVENT_CONTACTOR_WELDED) \
|
||||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||||
XX(EVENT_WATER_INGRESS) \
|
XX(EVENT_WATER_INGRESS) \
|
||||||
XX(EVENT_12V_LOW) \
|
XX(EVENT_12V_LOW) \
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;'>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
content += " Cont. Neg.: ";
|
||||||
|
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
|
||||||
|
content += "<span style='color: green;'>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
content += " Cont. Pos.: ";
|
||||||
|
if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) {
|
||||||
|
content += "<span style='color: green;'>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
content += "</h4>";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Close the block
|
// Close the block
|
||||||
|
@ -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;'>✕</span></h4>";
|
content += "<span style='color: red;'>✕</span></h4>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (emulator_pause_status == NORMAL)
|
||||||
|
content += "<h4>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||||
|
else
|
||||||
|
content += "<h4 style='color: red;'>Power status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
||||||
|
|
||||||
#ifdef CONTACTOR_CONTROL
|
#ifdef CONTACTOR_CONTROL
|
||||||
content += "<h4>Contactors controlled by Battery-Emulator: ";
|
content += "<h4>Contactors controlled by Battery-Emulator: ";
|
||||||
if (datalayer.system.status.contactor_control_closed) {
|
if (datalayer.system.status.contactor_control_closed) {
|
||||||
|
@ -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;'>✓</span>";
|
||||||
content += "<h4 style='color: red;'>Pause status: " + String(get_emulator_pause_status().c_str()) + " </h4>";
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
content += " Cont. Neg.: ";
|
||||||
|
if (digitalRead(NEGATIVE_CONTACTOR_PIN) == HIGH) {
|
||||||
|
content += "<span style='color: green;'>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
content += " Cont. Pos.: ";
|
||||||
|
if (digitalRead(POSITIVE_CONTACTOR_PIN) == HIGH) {
|
||||||
|
content += "<span style='color: green;'>✓</span>";
|
||||||
|
} else {
|
||||||
|
content += "<span style='color: red;'>✕</span>";
|
||||||
|
}
|
||||||
|
content += "</h4>";
|
||||||
|
#endif
|
||||||
|
|
||||||
content += "</div>";
|
content += "</div>";
|
||||||
content += "</div>";
|
content += "</div>";
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -27,6 +27,10 @@
|
||||||
#include "PYLON-CAN.h"
|
#include "PYLON-CAN.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef PYLON_LV_CAN
|
||||||
|
#include "PYLON-LV-CAN.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef SMA_CAN
|
#ifdef SMA_CAN
|
||||||
#include "SMA-CAN.h"
|
#include "SMA-CAN.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
142
Software/src/inverter/PYLON-LV-CAN.cpp
Normal file
142
Software/src/inverter/PYLON-LV-CAN.cpp
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
#include "../include.h"
|
||||||
|
#ifdef PYLON_LV_CAN
|
||||||
|
#include "../datalayer/datalayer.h"
|
||||||
|
#include "PYLON-LV-CAN.h"
|
||||||
|
|
||||||
|
/* Do not change code below unless you are sure what you are doing */
|
||||||
|
|
||||||
|
static unsigned long previousMillis1000ms = 0;
|
||||||
|
|
||||||
|
CAN_frame PYLON_351 = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 6,
|
||||||
|
.ID = 0x351,
|
||||||
|
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
|
CAN_frame PYLON_355 = {.FD = false, .ext_ID = false, .DLC = 4, .ID = 0x355, .data = {0x00, 0x00, 0x00, 0x00}};
|
||||||
|
CAN_frame PYLON_356 = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 6,
|
||||||
|
.ID = 0x356,
|
||||||
|
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||||
|
CAN_frame PYLON_359 = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 7,
|
||||||
|
.ID = 0x359,
|
||||||
|
.data = {0x00, 0x00, 0x00, 0x00, PACK_NUMBER, 'P', 'N'}};
|
||||||
|
CAN_frame PYLON_35C = {.FD = false, .ext_ID = false, .DLC = 2, .ID = 0x35C, .data = {0x00, 0x00}};
|
||||||
|
CAN_frame PYLON_35E = {.FD = false,
|
||||||
|
.ext_ID = false,
|
||||||
|
.DLC = 8,
|
||||||
|
.ID = 0x35E,
|
||||||
|
.data = {
|
||||||
|
MANUFACTURER_NAME[0],
|
||||||
|
MANUFACTURER_NAME[1],
|
||||||
|
MANUFACTURER_NAME[2],
|
||||||
|
MANUFACTURER_NAME[3],
|
||||||
|
MANUFACTURER_NAME[4],
|
||||||
|
MANUFACTURER_NAME[5],
|
||||||
|
MANUFACTURER_NAME[6],
|
||||||
|
MANUFACTURER_NAME[7],
|
||||||
|
}};
|
||||||
|
|
||||||
|
void update_values_can_inverter() {
|
||||||
|
// This function maps all the values fetched from battery CAN to the correct CAN messages
|
||||||
|
|
||||||
|
// do not update values unless we have some voltage, as we will run into IntegerDivideByZero exceptions otherwise
|
||||||
|
if (datalayer.battery.status.voltage_dV == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// TODO: officially this value is "battery charge voltage". Do we need to add something here to the actual voltage?
|
||||||
|
PYLON_351.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff;
|
||||||
|
PYLON_351.data.u8[1] = datalayer.battery.status.voltage_dV >> 8;
|
||||||
|
int16_t maxChargeCurrent = datalayer.battery.status.max_charge_power_W * 100 / datalayer.battery.status.voltage_dV;
|
||||||
|
PYLON_351.data.u8[2] = maxChargeCurrent & 0xff;
|
||||||
|
PYLON_351.data.u8[3] = maxChargeCurrent >> 8;
|
||||||
|
int16_t maxDischargeCurrent =
|
||||||
|
datalayer.battery.status.max_discharge_power_W * 100 / datalayer.battery.status.voltage_dV;
|
||||||
|
PYLON_351.data.u8[4] = maxDischargeCurrent & 0xff;
|
||||||
|
PYLON_351.data.u8[5] = maxDischargeCurrent >> 8;
|
||||||
|
|
||||||
|
PYLON_355.data.u8[0] = (datalayer.battery.status.reported_soc / 10) & 0xff;
|
||||||
|
PYLON_355.data.u8[1] = (datalayer.battery.status.reported_soc / 10) >> 8;
|
||||||
|
PYLON_355.data.u8[2] = (datalayer.battery.status.soh_pptt / 10) & 0xff;
|
||||||
|
PYLON_355.data.u8[3] = (datalayer.battery.status.soh_pptt / 10) >> 8;
|
||||||
|
|
||||||
|
PYLON_356.data.u8[0] = datalayer.battery.status.voltage_dV & 0xff;
|
||||||
|
PYLON_356.data.u8[1] = datalayer.battery.status.voltage_dV >> 8;
|
||||||
|
PYLON_356.data.u8[2] = datalayer.battery.status.current_dA & 0xff;
|
||||||
|
PYLON_356.data.u8[3] = datalayer.battery.status.current_dA >> 8;
|
||||||
|
PYLON_356.data.u8[4] = datalayer.battery.status.temperature_max_dC & 0xff;
|
||||||
|
PYLON_356.data.u8[5] = datalayer.battery.status.temperature_max_dC >> 8;
|
||||||
|
|
||||||
|
// initialize all errors and warnings to 0
|
||||||
|
PYLON_359.data.u8[0] = 0x00;
|
||||||
|
PYLON_359.data.u8[1] = 0x00;
|
||||||
|
PYLON_359.data.u8[2] = 0x00;
|
||||||
|
PYLON_359.data.u8[3] = 0x00;
|
||||||
|
PYLON_359.data.u8[4] = PACK_NUMBER;
|
||||||
|
PYLON_359.data.u8[5] = 'P';
|
||||||
|
PYLON_359.data.u8[6] = 'N';
|
||||||
|
|
||||||
|
// ERRORS
|
||||||
|
if (datalayer.battery.status.current_dA >= maxDischargeCurrent)
|
||||||
|
PYLON_359.data.u8[0] |= 0x80;
|
||||||
|
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE)
|
||||||
|
PYLON_359.data.u8[0] |= 0x10;
|
||||||
|
if (datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE)
|
||||||
|
PYLON_359.data.u8[0] |= 0x0C;
|
||||||
|
if (datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV)
|
||||||
|
PYLON_359.data.u8[0] |= 0x04;
|
||||||
|
// we never set PYLON_359.data.u8[1] |= 0x80 called "BMS internal"
|
||||||
|
if (datalayer.battery.status.current_dA <= -1 * maxChargeCurrent)
|
||||||
|
PYLON_359.data.u8[1] |= 0x01;
|
||||||
|
|
||||||
|
// WARNINGS (using same rules as errors but reporting earlier)
|
||||||
|
if (datalayer.battery.status.current_dA >= maxDischargeCurrent * WARNINGS_PERCENT / 100)
|
||||||
|
PYLON_359.data.u8[2] |= 0x80;
|
||||||
|
if (datalayer.battery.status.temperature_min_dC <= BATTERY_MINTEMPERATURE * WARNINGS_PERCENT / 100)
|
||||||
|
PYLON_359.data.u8[2] |= 0x10;
|
||||||
|
if (datalayer.battery.status.temperature_max_dC >= BATTERY_MAXTEMPERATURE * WARNINGS_PERCENT / 100)
|
||||||
|
PYLON_359.data.u8[2] |= 0x0C;
|
||||||
|
if (datalayer.battery.status.voltage_dV * 100 <= datalayer.battery.info.min_cell_voltage_mV + 100)
|
||||||
|
PYLON_359.data.u8[2] |= 0x04;
|
||||||
|
// we never set PYLON_359.data.u8[3] |= 0x80 called "BMS internal"
|
||||||
|
if (datalayer.battery.status.current_dA <= -1 * maxChargeCurrent * WARNINGS_PERCENT / 100)
|
||||||
|
PYLON_359.data.u8[3] |= 0x01;
|
||||||
|
|
||||||
|
PYLON_35C.data.u8[0] = 0xC0; // enable charging and discharging
|
||||||
|
PYLON_35C.data.u8[1] = 0x00;
|
||||||
|
if (datalayer.battery.status.real_soc <= datalayer.battery.settings.min_percentage)
|
||||||
|
PYLON_35C.data.u8[0] = 0xA0; // enable charing, set charge immediately
|
||||||
|
if (datalayer.battery.status.real_soc >= datalayer.battery.settings.max_percentage)
|
||||||
|
PYLON_35C.data.u8[0] = 0x40; // enable discharging only
|
||||||
|
|
||||||
|
// PYLON_35E is pre-filled with the manufacturer name
|
||||||
|
}
|
||||||
|
|
||||||
|
void receive_can_inverter(CAN_frame rx_frame) {
|
||||||
|
switch (rx_frame.ID) {
|
||||||
|
case 0x305: //Message originating from inverter.
|
||||||
|
// according to the spec, this message includes only 0-bytes
|
||||||
|
datalayer.system.status.CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_can_inverter() {
|
||||||
|
unsigned long currentMillis = millis();
|
||||||
|
|
||||||
|
if (currentMillis - previousMillis1000ms >= 1000) {
|
||||||
|
previousMillis1000ms = currentMillis;
|
||||||
|
|
||||||
|
transmit_can(&PYLON_351, can_config.inverter);
|
||||||
|
transmit_can(&PYLON_355, can_config.inverter);
|
||||||
|
transmit_can(&PYLON_356, can_config.inverter);
|
||||||
|
transmit_can(&PYLON_359, can_config.inverter);
|
||||||
|
transmit_can(&PYLON_35C, can_config.inverter);
|
||||||
|
transmit_can(&PYLON_35E, can_config.inverter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
16
Software/src/inverter/PYLON-LV-CAN.h
Normal file
16
Software/src/inverter/PYLON-LV-CAN.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#ifndef PYLON_LV_CAN_H
|
||||||
|
#define PYLON_LV_CAN_H
|
||||||
|
#include "../include.h"
|
||||||
|
|
||||||
|
#define CAN_INVERTER_SELECTED
|
||||||
|
|
||||||
|
#define MANUFACTURER_NAME "BatEmuLV"
|
||||||
|
#define PACK_NUMBER 0x01
|
||||||
|
// 80 means after reaching 80% of a nominal value a warning is produced (e.g. 80% of max current)
|
||||||
|
#define WARNINGS_PERCENT 80
|
||||||
|
|
||||||
|
void send_system_data();
|
||||||
|
void send_setup_info();
|
||||||
|
void transmit_can(CAN_frame* tx_frame, int interface);
|
||||||
|
|
||||||
|
#endif
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue