Merge remote-tracking branch 'origin' into feature/bmwi3

This commit is contained in:
Daniel 2024-02-23 15:11:41 +02:00
commit 25b0a7da65
78 changed files with 10827 additions and 2100 deletions

View file

@ -49,6 +49,7 @@ jobs:
# - LUNA2000_MODBUS
# - PYLON_CAN
# - SMA_CAN
# - SMA_TRIPOWER_CAN
# - SOFAR_CAN
# - SOLAX_CAN

View file

@ -52,8 +52,10 @@ jobs:
- LUNA2000_MODBUS
- PYLON_CAN
- SMA_CAN
- SMA_TRIPOWER_CAN
- SOFAR_CAN
- SOLAX_CAN
- SERIAL_LINK_TRANSMITTER
# This is the platform GitHub will use to run our workflow.
runs-on: ubuntu-latest

View file

@ -47,8 +47,10 @@ jobs:
- LUNA2000_MODBUS
- PYLON_CAN
- SMA_CAN
- SMA_TRIPOWER_CAN
- SOFAR_CAN
- SOLAX_CAN
- SERIAL_LINK_TRANSMITTER
# This is the platform GitHub will use to run our workflow.
runs-on: ubuntu-latest

30
.github/workflows/unit-tests.yml vendored Normal file
View file

@ -0,0 +1,30 @@
name: Run Unit Tests
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure and build with CMake
run: |
mkdir build
cd build
cmake ..
cmake --build .
- name: Run unit tests
run: |
set -e # Exit immediately on non-zero exit code
cd build/test
dir -s
for test_executable in *; do
if [ -f "$test_executable" ] && [ -x "$test_executable" ]; then
./"$test_executable"
fi
done

8
.gitignore vendored
View file

@ -1,5 +1,9 @@
# Ignore any .vscode folder
*.vscode/
# Ignore any files in the build folder
Software/build/
# Ignore any files in any build folder
*build/
# Ignore .exe (unit tests)
*.exe
**/.DS_Store

10
CMakeLists.txt Normal file
View file

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.10)
# Set the C++ standard to C++20
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(BatteryEmulator)
# add_subdirectory(Software/src/devboard/utils)
add_subdirectory(test)

View file

@ -11,6 +11,7 @@
#include "src/devboard/utils/events.h"
#include "src/inverter/INVERTERS.h"
#include "src/lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h"
#include "src/lib/bblanchon-ArduinoJson/ArduinoJson.h"
#include "src/lib/eModbus-eModbus/Logging.h"
#include "src/lib/eModbus-eModbus/ModbusServerRTU.h"
#include "src/lib/eModbus-eModbus/scripts/mbServerFCs.h"
@ -21,8 +22,8 @@
#include "src/devboard/webserver/webserver.h"
#endif
Preferences settings; // Store user settings
const char* version_number = "5.2.0"; // The current software version, shown on webserver
Preferences settings; // Store user settings
const char* version_number = "5.3.RC"; // The current software version, shown on webserver
// Interval settings
int intervalUpdateValues = 4800; // Interval at which to update inverter values / Modbus registers
const int interval10 = 10; // Interval for 10ms tasks
@ -53,27 +54,27 @@ uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
ModbusServerRTU MBserver(Serial2, 2000);
#endif
// Common inverter parameters. Batteries map their values to these variables
uint16_t max_voltage = ABSOLUTE_MAX_VOLTAGE; // If higher charging is not possible (goes into forced discharge)
uint16_t min_voltage = ABSOLUTE_MIN_VOLTAGE; // If lower disables discharging battery
uint16_t battery_voltage = 3700; //V+1, 0-500.0 (0-5000)
uint16_t battery_current = 0;
uint16_t SOC = 5000; //SOC%, 0-100.00 (0-10000)
uint16_t StateOfHealth = 9900; //SOH%, 0-100.00 (0-10000)
uint16_t capacity_Wh = BATTERY_WH_MAX; //Wh, 0-60000
uint16_t remaining_capacity_Wh = BATTERY_WH_MAX; //Wh, 0-60000
uint16_t max_target_discharge_power = 0; // 0W (0W > restricts to no discharge), Updates later on from CAN
uint16_t max_target_charge_power = 4312; // Init to 4.3kW, Updates later on from CAN
uint16_t temperature_max = 50; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
uint16_t temperature_min = 60; // Reads from battery later
uint8_t bms_char_dis_status = STANDBY; // 0 standby, 1 discharging, 2, charging
uint8_t bms_status = ACTIVE; // ACTIVE - [0..5]<>[STANDBY,INACTIVE,DARKSTART,ACTIVE,FAULT,UPDATING]
uint16_t stat_batt_power = 0; // Power going in/out of battery
uint16_t cell_max_voltage = 3700; // Stores the highest cell voltage value in the system
uint16_t cell_min_voltage = 3700; // Stores the minimum cell voltage value in the system
uint16_t cellvoltages[120]; // Stores all cell voltages
uint8_t nof_cellvoltages = 0; // Total number of cell voltages, set by each battery.
bool LFP_Chemistry = false;
// Common system parameters. Batteries map their values to these variables
uint32_t system_capacity_Wh = BATTERY_WH_MAX; //Wh, 0-150000Wh
uint32_t system_remaining_capacity_Wh = BATTERY_WH_MAX; //Wh, 0-150000Wh
int16_t system_temperature_max_dC = 0; //C+1, -50.0 - 50.0
int16_t system_temperature_min_dC = 0; //C+1, -50.0 - 50.0
int16_t system_active_power_W = 0; //Watts, -32000 to 32000
int16_t system_battery_current_dA = 0; //A+1, -1000 - 1000
uint16_t system_battery_voltage_dV = 3700; //V+1, 0-500.0 (0-5000)
uint16_t system_max_design_voltage_dV = 5000; //V+1, 0-500.0 (0-5000)
uint16_t system_min_design_voltage_dV = 2500; //V+1, 0-500.0 (0-5000)
uint16_t system_scaled_SOC_pptt = 5000; //SOC%, 0-100.00 (0-10000)
uint16_t system_real_SOC_pptt = 5000; //SOC%, 0-100.00 (0-10000)
uint16_t system_SOH_pptt = 9900; //SOH%, 0-100.00 (0-10000)
uint16_t system_max_discharge_power_W = 0; //Watts, 0 to 65535
uint16_t system_max_charge_power_W = 4312; //Watts, 0 to 65535
uint16_t system_cell_max_voltage_mV = 3700; //mV, 0-5000 , Stores the highest cell millivolt value
uint16_t system_cell_min_voltage_mV = 3700; //mV, 0-5000, Stores the minimum cell millivolt value
uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV. Oversized to accomodate all setups
uint8_t system_bms_status = ACTIVE; //ACTIVE - [0..5]<>[STANDBY,INACTIVE,DARKSTART,ACTIVE,FAULT,UPDATING]
uint8_t system_number_of_cells = 0; //Total number of cell voltages, set by each battery
bool system_LFP_Chemistry = false; //Set to true or false depending on cell chemistry
// Common charger parameters
volatile float charger_setpoint_HV_VDC = 0.0f;
@ -96,6 +97,7 @@ static uint8_t brightness = 0;
static bool rampUp = true;
const uint8_t maxBrightness = 100;
uint8_t LEDcolor = GREEN;
bool test_all_colors = false;
// Contactor parameters
#ifdef CONTACTOR_CONTROL
@ -144,11 +146,10 @@ void setup() {
inform_user_on_inverter();
inform_user_on_battery();
#ifdef BATTERY_HAS_INIT
init_battery();
#endif
// BOOT button at runtime is used as an input for various things
pinMode(0, INPUT_PULLUP);
}
// Perform main program functions
@ -185,9 +186,10 @@ void loop() {
if (millis() - previousMillisUpdateVal >= intervalUpdateValues) // Every 4.8s
{
previousMillisUpdateVal = millis();
update_SOC(); // Check if real or calculated SOC% value should be sent
update_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus.
if (DUMMY_EVENT_ENABLED) {
set_event(EVENT_DUMMY, (uint8_t)millis());
set_event(EVENT_DUMMY_ERROR, (uint8_t)millis());
}
}
@ -196,7 +198,13 @@ void loop() {
#ifdef DUAL_CAN
send_can2();
#endif
update_event_timestamps();
run_event_handling();
if (digitalRead(0) == HIGH) {
test_all_colors = false;
} else {
test_all_colors = true;
}
}
// Initialization functions
@ -214,7 +222,7 @@ void init_stored_settings() {
settings.clear(); // If this clear function is executed, no settings will be read from storage
#endif
static uint16_t temp = 0;
static uint32_t temp = 0;
temp = settings.getUInt("BATTERY_WH_MAX", false);
if (temp != 0) {
BATTERY_WH_MAX = temp;
@ -234,7 +242,10 @@ void init_stored_settings() {
temp = settings.getUInt("MAXDISCHARGEAMP", false);
if (temp != 0) {
MAXDISCHARGEAMP = temp;
}
temp = settings.getBool("USE_SCALED_SOC", false);
USE_SCALED_SOC = temp; //This bool needs to be checked inside the temp!= block
} // No way to know if it wasnt reset otherwise
settings.end();
}
@ -335,6 +346,9 @@ void inform_user_on_inverter() {
#ifdef SMA_CAN
Serial.println("SMA CAN protocol selected");
#endif
#ifdef SMA_TRIPOWER_CAN
Serial.println("SMA Tripower CAN protocol selected");
#endif
#ifdef SOFAR_CAN
Serial.println("SOFAR CAN protocol selected");
#endif
@ -345,48 +359,11 @@ void inform_user_on_inverter() {
#endif
}
void inform_user_on_battery() {
// Inform user what battery is used
#ifdef BMW_I3_BATTERY
Serial.println("BMW i3 battery selected");
pinMode(WUP_PIN, OUTPUT); //This pin used for WUP relay
digitalWrite(WUP_PIN, LOW);
#ifdef CONTACTOR_CONTROL
// Contactor control cannot be used when WUP signal is sent on GPIO pins
#error CONTACTOR CONTROL CANNOT BE USED ON BMW i3
#endif
#endif
#ifdef CHADEMO_BATTERY
Serial.println("Chademo battery selected");
#endif
#ifdef IMIEV_CZERO_ION_BATTERY
Serial.println("Mitsubishi i-MiEV / Citroen C-Zero / Peugeot Ion battery selected");
#endif
#ifdef KIA_HYUNDAI_64_BATTERY
Serial.println("Kia Niro / Hyundai Kona 64kWh battery selected");
#endif
#ifdef NISSAN_LEAF_BATTERY
Serial.println("Nissan LEAF battery selected");
#endif
#ifdef RENAULT_KANGOO_BATTERY
Serial.println("Renault Kangoo battery selected");
#endif
#ifdef SANTA_FE_PHEV_BATTERY
Serial.println("Hyundai Santa Fe PHEV battery selected");
#endif
#ifdef RENAULT_ZOE_BATTERY
Serial.println("Renault Zoe battery selected");
#endif
#ifdef TESLA_MODEL_3_BATTERY
Serial.println("Tesla Model 3 battery selected");
#endif
#ifdef TEST_FAKE_BATTERY
Serial.println("Test mode with fake battery selected");
#endif
#ifdef SERIAL_LINK_RECEIVER
Serial.println("SERIAL_DATA_LINK_RECEIVER selected");
#endif
#if !defined(ABSOLUTE_MAX_VOLTAGE)
void init_battery() {
// Inform user what battery is used and perform setup
setup_battery();
#ifndef BATTERY_SELECTED
#error No battery selected! Choose one from the USER_SETTINGS.h file
#endif
}
@ -397,37 +374,10 @@ void receive_can() { // This section checks if we have a complete CAN message i
CAN_frame_t rx_frame;
if (xQueueReceive(CAN_cfg.rx_queue, &rx_frame, 3 * portTICK_PERIOD_MS) == pdTRUE) {
if (rx_frame.FIR.B.FF == CAN_frame_std) {
//printf("New standard frame");
// Battery
#ifdef BMW_I3_BATTERY
receive_can_i3_battery(rx_frame);
#endif
#ifdef CHADEMO_BATTERY
receive_can_chademo_battery(rx_frame);
#endif
#ifdef IMIEV_CZERO_ION_BATTERY
receive_can_imiev_battery(rx_frame);
#endif
#ifdef KIA_HYUNDAI_64_BATTERY
receive_can_kiaHyundai_64_battery(rx_frame);
#endif
#ifdef NISSAN_LEAF_BATTERY
receive_can_leaf_battery(rx_frame);
#endif
#ifdef RENAULT_KANGOO_BATTERY
receive_can_kangoo_battery(rx_frame);
#endif
#ifdef SANTA_FE_PHEV_BATTERY
receive_can_santafe_phev_battery(rx_frame);
#endif
#ifdef RENAULT_ZOE_BATTERY
receive_can_zoe_battery(rx_frame);
#endif
#ifdef TESLA_MODEL_3_BATTERY
receive_can_tesla_model_3_battery(rx_frame);
#endif
#ifdef TEST_FAKE_BATTERY
receive_can_test_battery(rx_frame);
//printf("New standard frame");
// Battery
#ifndef SERIAL_LINK_RECEIVER
receive_can_battery(rx_frame);
#endif
// Inverter
#ifdef BYD_CAN
@ -435,6 +385,9 @@ void receive_can() { // This section checks if we have a complete CAN message i
#endif
#ifdef SMA_CAN
receive_can_sma(rx_frame);
#endif
#ifdef SMA_TRIPOWER_CAN
receive_can_sma_tripower(rx_frame);
#endif
// Charger
#ifdef CHEVYVOLT_CHARGER
@ -467,40 +420,14 @@ void send_can() {
#ifdef SMA_CAN
send_can_sma();
#endif
#ifdef SMA_TRIPOWER_CAN
send_can_sma_tripower();
#endif
#ifdef SOFAR_CAN
send_can_sofar();
#endif
// Battery
#ifdef BMW_I3_BATTERY
send_can_i3_battery();
#endif
#ifdef CHADEMO_BATTERY
send_can_chademo_battery();
#endif
#ifdef IMIEV_CZERO_ION_BATTERY
send_can_imiev_battery();
#endif
#ifdef KIA_HYUNDAI_64_BATTERY
send_can_kiaHyundai_64_battery();
#endif
#ifdef NISSAN_LEAF_BATTERY
send_can_leaf_battery();
#endif
#ifdef RENAULT_KANGOO_BATTERY
send_can_kangoo_battery();
#endif
#ifdef SANTA_FE_PHEV_BATTERY
send_can_santafe_phev_battery();
#endif
#ifdef RENAULT_ZOE_BATTERY
send_can_zoe_battery();
#endif
#ifdef TESLA_MODEL_3_BATTERY
send_can_tesla_model_3_battery();
#endif
#ifdef TEST_FAKE_BATTERY
send_can_test_battery();
#endif
send_can_battery();
#ifdef CHEVYVOLT_CHARGER
send_can_chevyvolt_charger();
#endif
@ -563,30 +490,30 @@ void handle_LED_state() {
} else if (!rampUp && brightness == 0) {
rampUp = true;
}
switch (LEDcolor) {
case GREEN:
pixels.setPixelColor(0, pixels.Color(0, brightness, 0)); // Green pulsing LED
break;
case YELLOW:
pixels.setPixelColor(0, pixels.Color(brightness, brightness, 0)); // Yellow pulsing LED
break;
case BLUE:
pixels.setPixelColor(0, pixels.Color(0, 0, brightness)); // Blue pulsing LED
break;
case RED:
pixels.setPixelColor(0, pixels.Color(150, 0, 0)); // Red LED full brightness
break;
case TEST_ALL_COLORS:
pixels.setPixelColor(0, pixels.Color(brightness, abs((100 - brightness)), abs((50 - brightness)))); // RGB
break;
default:
break;
}
// BMS in fault state overrides everything
if (bms_status == FAULT) {
LEDcolor = RED;
pixels.setPixelColor(0, pixels.Color(255, 0, 0)); // Red LED full brightness
if (test_all_colors == false) {
switch (get_event_level()) {
case EVENT_LEVEL_INFO:
LEDcolor = GREEN;
pixels.setPixelColor(0, pixels.Color(0, brightness, 0)); // Green pulsing LED
break;
case EVENT_LEVEL_WARNING:
LEDcolor = YELLOW;
pixels.setPixelColor(0, pixels.Color(brightness, brightness, 0)); // Yellow pulsing LED
break;
case EVENT_LEVEL_DEBUG:
case EVENT_LEVEL_UPDATE:
LEDcolor = BLUE;
pixels.setPixelColor(0, pixels.Color(0, 0, brightness)); // Blue pulsing LED
break;
case EVENT_LEVEL_ERROR:
LEDcolor = RED;
pixels.setPixelColor(0, pixels.Color(150, 0, 0)); // Red LED full brightness
break;
default:
break;
}
} else {
pixels.setPixelColor(0, pixels.Color(brightness, abs((100 - brightness)), abs((50 - brightness)))); // RGB
}
pixels.show(); // This sends the updated pixel color to the hardware.
@ -595,7 +522,7 @@ void handle_LED_state() {
#ifdef CONTACTOR_CONTROL
void handle_contactors() {
// First check if we have any active errors, incase we do, turn off the battery
if (bms_status == FAULT) {
if (system_bms_status == FAULT) {
timeSpentInFaultedMode++;
} else {
timeSpentInFaultedMode = 0;
@ -678,38 +605,26 @@ void handle_contactors() {
}
#endif
void update_SOC() {
if (USE_SCALED_SOC) { //User has configred a SOC window. Calculate a SOC% to send towards inverter
static int16_t CalculatedSOC = 0;
CalculatedSOC = system_real_SOC_pptt;
CalculatedSOC = (10000) * (CalculatedSOC - (MINPERCENTAGE * 10)) / (MAXPERCENTAGE * 10 - MINPERCENTAGE * 10);
if (CalculatedSOC < 0) { //We are in the real SOC% range of 0-MINPERCENTAGE%
CalculatedSOC = 0;
}
if (CalculatedSOC > 10000) { //We are in the real SOC% range of MAXPERCENTAGE-100%
CalculatedSOC = 10000;
}
system_scaled_SOC_pptt = CalculatedSOC;
} else { // No SOC window wanted. Set scaled to same as real.
system_scaled_SOC_pptt = system_real_SOC_pptt;
}
}
void update_values() {
// Battery
#ifdef BMW_I3_BATTERY
update_values_i3_battery(); // Map the values to the correct registers
#endif
#ifdef CHADEMO_BATTERY
update_values_chademo_battery(); // Map the values to the correct registers
#endif
#ifdef IMIEV_CZERO_ION_BATTERY
update_values_imiev_battery(); // Map the values to the correct registers
#endif
#ifdef KIA_HYUNDAI_64_BATTERY
update_values_kiaHyundai_64_battery(); // Map the values to the correct registers
#endif
#ifdef NISSAN_LEAF_BATTERY
update_values_leaf_battery(); // Map the values to the correct registers
#endif
#ifdef RENAULT_KANGOO_BATTERY
update_values_kangoo_battery(); // Map the values to the correct registers
#endif
#ifdef SANTA_FE_PHEV_BATTERY
update_values_santafe_phev_battery(); // Map the values to the correct registers
#endif
#ifdef RENAULT_ZOE_BATTERY
update_values_zoe_battery(); // Map the values to the correct registers
#endif
#ifdef TESLA_MODEL_3_BATTERY
update_values_tesla_model_3_battery(); // Map the values to the correct registers
#endif
#ifdef TEST_FAKE_BATTERY
update_values_test_battery(); // Map the fake values to the correct registers
#endif
update_values_battery(); // Map the fake values to the correct registers
// Inverter
#ifdef BYD_CAN
update_values_can_byd();
@ -726,6 +641,9 @@ void update_values() {
#ifdef SMA_CAN
update_values_can_sma();
#endif
#ifdef SMA_TRIPOWER_CAN
update_values_can_sma_tripower();
#endif
#ifdef SOFAR_CAN
update_values_can_sofar();
#endif
@ -734,23 +652,22 @@ void update_values() {
#endif
}
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
void runSerialDataLink() {
static unsigned long updateTime = 0;
unsigned long currentMillis = millis();
#ifdef SERIAL_LINK_RECEIVER
if ((currentMillis - updateTime) > 1) { //Every 2ms
updateTime = currentMillis;
manageSerialLinkReceiver();
}
#endif
#ifdef SERIAL_LINK_TRANSMITTER
if ((currentMillis - updateTime) > 1) { //Every 2ms
updateTime = currentMillis;
manageSerialLinkTransmitter();
}
#ifdef SERIAL_LINK_RECEIVER
manageSerialLinkReceiver();
#endif
#ifdef SERIAL_LINK_TRANSMITTER
manageSerialLinkTransmitter();
#endif
}
}
#endif
void init_serialDataLink() {
#if defined(SERIAL_LINK_RECEIVER) || defined(SERIAL_LINK_TRANSMITTER)
@ -765,5 +682,7 @@ void storeSettings() {
settings.putUInt("MINPERCENTAGE", MINPERCENTAGE);
settings.putUInt("MAXCHARGEAMP", MAXCHARGEAMP);
settings.putUInt("MAXDISCHARGEAMP", MAXDISCHARGEAMP);
settings.putBool("USE_SCALED_SOC", USE_SCALED_SOC);
settings.end();
}

View file

@ -4,16 +4,17 @@
/* They can be defined here, or later on in the WebUI */
/* Battery settings */
volatile uint16_t BATTERY_WH_MAX =
30000; //Battery size in Wh (Maximum value for most inverters is 65000 [65kWh], you can use larger batteries but do not set value over 65000!
volatile bool USE_SCALED_SOC =
true; //Increases battery life. If true will rescale SOC between the configured min/max-percentage
volatile uint32_t BATTERY_WH_MAX = 30000; //Battery size in Wh
volatile uint16_t MAXPERCENTAGE =
800; //80.0% , Max percentage the battery will charge to (App will show 100% once this value is reached)
800; //80.0% , Max percentage the battery will charge to (Inverter gets 100% when reached)
volatile uint16_t MINPERCENTAGE =
200; //20.0% , Min percentage the battery will discharge to (App will show 0% once this value is reached)
200; //20.0% , Min percentage the battery will discharge to (Inverter gets 0% when reached)
volatile uint16_t MAXCHARGEAMP =
300; //30.0A , BYD CAN specific setting, Max charge speed in Amp (Some inverters needs to be artificially limited)
300; //30.0A , BYD CAN specific setting, Max charge in Amp (Some inverters needs to be limited)
volatile uint16_t MAXDISCHARGEAMP =
300; //30.0A , BYD CAN specific setting, Max discharge speed in Amp (Some inverters needs to be artificially limited)
300; //30.0A , BYD CAN specific setting, Max discharge in Amp (Some inverters needs to be limited)
/* Charger settings (Optional, when generator charging) */
/* Charger settings */

View file

@ -25,11 +25,12 @@
//#define LUNA2000_MODBUS //Enable this line to emulate a "Luna2000 battery" over Modbus RTU
//#define PYLON_CAN //Enable this line to emulate a "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_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 SOLAX_CAN //Enable this line to emulate a "SolaX Triple Power LFP" over CAN bus
/* Other options */
#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs
//#define DEBUG_VIA_USB //Enable this line to have the USB port output serial diagnostic data while program runs
//#define INTERLOCK_REQUIRED //Nissan LEAF specific setting, if enabled requires both high voltage conenctors to be seated before starting
//#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM logic for contactors, which lower power consumption and heat generation
@ -37,10 +38,10 @@
//#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 WEBSERVER //Enable this line to enable WiFi, and to run the webserver. See USER_SETTINGS.cpp for the Wifi settings.
#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot
//#define LOAD_SAVED_SETTINGS_ON_BOOT //Enable this line to read settings stored via the webserver on boot (overrides any battery settings set in USER_SETTINGS.cpp)
/* MQTT options */
//#define MQTT // Enable this line to enable MQTT
// #define MQTT // Enable this line to enable MQTT
#define MQTT_SUBSCRIPTIONS \
{ "my/topic/abc", "my/other/topic" }
#define MQTT_SERVER "192.168.xxx.yyy"
@ -54,12 +55,13 @@
//#define NISSANLEAF_CHARGER //Enable this line to control a Nissan LEAF PDM connected to battery - for example, when generator charging
/* Battery limits: These are set in the USER_SETTINGS.cpp file, or later on via the Webserver */
extern volatile uint16_t BATTERY_WH_MAX;
extern volatile uint32_t BATTERY_WH_MAX;
extern volatile uint16_t MAXPERCENTAGE;
extern volatile uint16_t MINPERCENTAGE;
extern volatile uint16_t MAXCHARGEAMP;
extern volatile uint16_t MAXDISCHARGEAMP;
extern volatile uint8_t AccessPointEnabled;
extern volatile bool USE_SCALED_SOC;
/* Charger limits (Optional): Set in the USER_SETTINGS.cpp or later in the webserver */
extern volatile float charger_setpoint_HV_VDC;

View file

@ -21,7 +21,6 @@
#ifdef NISSAN_LEAF_BATTERY
#include "NISSAN-LEAF-BATTERY.h" //See this file for more LEAF battery settings
#define BATTERY_HAS_INIT
#endif
#ifdef RENAULT_KANGOO_BATTERY
@ -48,4 +47,13 @@
#include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h" //See this file for more Serial-battery settings
#endif
#ifdef SERIAL_LINK_RECEIVER // The serial thing does its thing
void receive_can_battery();
#else
void receive_can_battery(CAN_frame_t rx_frame);
#endif
void update_values_battery();
void send_can_battery();
void setup_battery(void);
#endif

View file

@ -1,10 +1,9 @@
#include "BMW-I3-BATTERY.h"
#include "BATTERIES.h"
#ifdef BMW_I3_BATTERY
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
//TODO: before using
// Map the final values in update_values_i3_battery, set some to static values if not available (current, discharge max , charge max)
#include "BMW-I3-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
@ -40,79 +39,6 @@ unsigned long turnOnTime; // Variables to store timestamps
enum State { POWERON, STATE_ON, STATE_OFF, STATE_STAY_ON };
static State WUPState = POWERON;
#define LB_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Inverter
#define LB_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Inverter
CAN_frame_t BMW_000 = {.FIR = {.B =
{
.DLC = 4,
.FF = CAN_frame_std,
}},
.MsgID = 0x000,
.data = {0x10, 0x44, 0x00, 0x01}};
CAN_frame_t BMW_0A5 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x0A5,
.data = {0x47, 0xF0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF}};
CAN_frame_t BMW_0A8 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x0A8,
.data = {0xD1, 0xF2, 0xFF, 0xBF, 0x5D, 0xE8, 0xD3, 0xC3}};
CAN_frame_t BMW_0AA = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x0AA,
.data = {0x00, 0xFC, 0x00, 0x7D, 0xC0, 0x5D, 0xD0, 0xF7}};
CAN_frame_t BMW_0AD = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x0AD,
.data = {0xFF, 0xFF, 0xFE, 0xE7, 0x7F, 0xFE, 0x7F, 0xFF}};
CAN_frame_t BMW_0BB = {.FIR = {.B =
{
.DLC = 3,
.FF = CAN_frame_std,
}},
.MsgID = 0x0BB,
.data = {0x7D, 0xFF, 0xFF}};
CAN_frame_t BMW_0CD = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x0CD,
.data = {0xFF, 0xFF, 0xD0, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF}};
CAN_frame_t BMW_100 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x100,
.data = {0x9D, 0xF0, 0x7F, 0xC0, 0x5D, 0xA1, 0x87, 0x70}};
CAN_frame_t BMW_105 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x105,
.data = {0x03, 0xF0, 0x7F, 0xE0, 0x2E, 0x00, 0xFC, 0x0F}};
CAN_frame_t BMW_108 = {.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x108,
.data = {0xDD, 0x7D, 0xFF, 0x2C, 0x48, 0xF3, 0xFF, 0xFF}};
CAN_frame_t BMW_10B = {.FIR = {.B =
{
.DLC = 3,
@ -613,59 +539,47 @@ static uint16_t MaxDischargeVoltage = 0;
static uint16_t MaxChargeWattMaybe = 0;
static uint16_t MaxDischargeWattMaybe = 0;
void update_values_i3_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
bms_status = ACTIVE; //Startout in active mode
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
//Calculate the SOC% value to send to inverter
Calculated_SOC = (Display_SOC * 10); //Increase decimal amount
Calculated_SOC =
LB_MIN_SOC + (LB_MAX_SOC - LB_MIN_SOC) * (Calculated_SOC - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
if (Calculated_SOC < 0) { //We are in the real SOC% range of 0-20%, always set SOC sent to inverter as 0%
Calculated_SOC = 0;
}
if (Calculated_SOC > 1000) { //We are in the real SOC% range of 80-100%, always set SOC sent to inverter as 100%
Calculated_SOC = 1000;
}
SOC = (Calculated_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
system_real_SOC_pptt = (Display_SOC * 100); //increase Display_SOC range from 0-100 -> 100.00
battery_voltage = Battery_Volts; //Unit V+1 (5000 = 500.0V)
system_battery_voltage_dV = Battery_Volts; //Unit V+1 (5000 = 500.0V)
battery_current = Battery_Current;
system_battery_current_dA = Battery_Current;
capacity_Wh = BATTERY_WH_MAX;
system_capacity_Wh = BATTERY_WH_MAX;
remaining_capacity_Wh = (Battery_Capacity_kWh * 1000);
system_remaining_capacity_Wh = (Battery_Capacity_kWh * 1000);
if (SOC > 9900) //If Soc is over 99%, stop charging
if (system_scaled_SOC_pptt > 9900) //If Soc is over 99%, stop charging
{
max_target_charge_power = 0;
system_max_charge_power_W = 0;
} else {
max_target_charge_power = 5000; //Hardcoded value for testing. TODO: read real value from battery when discovered
system_max_charge_power_W = 5000; //Hardcoded value for testing. TODO: read real value from battery when discovered
}
if (SOC < 500) //If Soc is under 5%, stop dicharging
if (system_scaled_SOC_pptt < 500) //If Soc is under 5%, stop dicharging
{
max_target_discharge_power = 0;
system_max_discharge_power_W = 0;
} else {
max_target_discharge_power =
system_max_discharge_power_W =
5000; //Hardcoded value for testing. TODO: read real value from battery when discovered
}
Battery_Power = (Battery_Current * (Battery_Volts / 10));
stat_batt_power = Battery_Power; //TODO:, is mapping OK?
system_active_power_W = Battery_Power; //TODO:, is mapping OK?
temperature_min; //hardcoded to 5*C in startup, TODO:, find from battery CAN later
system_temperature_min_dC; //hardcoded to 5*C in startup, TODO:, find from battery CAN later
temperature_max; //hardcoded to 6*C in startup, TODO:, find from battery CAN later
system_temperature_max_dC; //hardcoded to 6*C in startup, TODO:, find from battery CAN later
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
bms_status = FAULT;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
set_event(EVENT_CAN_FAILURE, 0);
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
#ifdef DEBUG_VIA_USB
@ -694,16 +608,19 @@ void update_values_i3_battery() { //This function maps all the values fetched v
Serial.println(" ");
Serial.print("Values sent to inverter: ");
Serial.print("SOC%: ");
Serial.print(SOC);
Serial.print(system_scaled_SOC_pptt);
Serial.print(" Battery voltage: ");
Serial.print(system_battery_voltage_dV);
Serial.print(" Remaining Wh: ");
Serial.print(system_remaining_capacity_Wh);
Serial.print(" Max charge power: ");
Serial.print(max_target_charge_power);
Serial.print(system_max_charge_power_W);
Serial.print(" Max discharge power: ");
Serial.print(max_target_discharge_power);
Serial.print(system_max_discharge_power_W);
#endif
}
void receive_can_i3_battery(CAN_frame_t rx_frame) {
void receive_can_battery(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0x112: //BMS status [10ms]
CANstillAlive = 12; //This message is only sent if 30C signal is active
@ -759,7 +676,7 @@ void receive_can_i3_battery(CAN_frame_t rx_frame) {
break;
}
}
void send_can_i3_battery() {
void send_can_battery() {
unsigned long currentMillis = millis();
//Handle WUP signal
@ -1072,3 +989,12 @@ void send_can_i3_battery() {
ESP32Can.CANWriteFrame(&BMW_3E5);
}
}
void setup_battery(void) { // Performs one time setup at startup
Serial.println("BMW i3 battery selected");
system_max_design_voltage_dV = 4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
}
#endif

View file

@ -5,30 +5,30 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define ABSOLUTE_MAX_VOLTAGE \
4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
#define BATTERY_SELECTED
// These parameters need to be mapped for the inverter
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint8_t bms_status;
extern uint8_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
void update_values_i3_battery();
void receive_can_i3_battery(CAN_frame_t rx_frame);
void send_can_i3_battery();
void setup_battery(void);
#endif

View file

@ -1,7 +1,9 @@
#include "CHADEMO-BATTERY.h"
#include "BATTERIES.h"
#ifdef CHADEMO_BATTERY
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "CHADEMO-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
@ -89,28 +91,26 @@ uint8_t DynamicControlStatus = 0;
uint8_t HighCurrentControlStatus = 0;
uint8_t HighVoltageControlStatus = 0;
void update_values_chademo_battery() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
bms_status = ACTIVE; //Startout in active mode
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for the inverter
SOC = ChargingRate;
system_real_SOC_pptt = ChargingRate;
max_target_discharge_power = (MaximumDischargeCurrent * MaximumBatteryVoltage); //In Watts, Convert A to P
system_max_discharge_power_W = (MaximumDischargeCurrent * MaximumBatteryVoltage); //In Watts, Convert A to P
battery_voltage = TargetBatteryVoltage; //TODO: scaling?
system_battery_voltage_dV = TargetBatteryVoltage; //TODO: scaling?
capacity_Wh = ((RatedBatteryCapacity / 0.11) *
1000); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
system_capacity_Wh = ((RatedBatteryCapacity / 0.11) *
1000); //(Added in CHAdeMO v1.0.1), maybe handle hardcoded on lower protocol version?
remaining_capacity_Wh = (SOC / 100) * capacity_Wh;
system_remaining_capacity_Wh = (system_real_SOC_pptt / 100) * system_capacity_Wh;
/* Check if the Vehicle is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
bms_status = FAULT;
errorCode = 7;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
set_event(EVENT_CAN_FAILURE, 0);
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
#ifdef DEBUG_VIA_USB
@ -119,36 +119,23 @@ void update_values_chademo_battery() { //This function maps all the values fetc
Serial.println(errorCode);
}
Serial.print("BMS Status (3=OK): ");
Serial.println(bms_status);
switch (bms_char_dis_status) {
case 0:
Serial.println("Battery Idle");
break;
case 1:
Serial.println("Battery Discharging");
break;
case 2:
Serial.println("Battery Charging");
break;
default:
break;
}
Serial.println(system_bms_status);
Serial.print("Max discharge power: ");
Serial.println(max_target_discharge_power);
Serial.println(system_max_discharge_power_W);
Serial.print("Max charge power: ");
Serial.println(max_target_charge_power);
Serial.println(system_max_charge_power_W);
Serial.print("SOH%: ");
Serial.println(StateOfHealth);
Serial.println(system_SOH_pptt);
Serial.print("SOC% to Inverter: ");
Serial.println(SOC);
Serial.println(system_scaled_SOC_pptt);
Serial.print("Temperature Min: ");
Serial.println(temperature_min);
Serial.println(system_temperature_min_dC);
Serial.print("Temperature Max: ");
Serial.println(temperature_max);
Serial.println(system_temperature_max_dC);
#endif
}
void receive_can_chademo_battery(CAN_frame_t rx_frame) {
void receive_can_battery(CAN_frame_t rx_frame) {
CANstillAlive == 12; //We are getting CAN messages from the vehicle, inform the watchdog
switch (rx_frame.MsgID) {
@ -204,7 +191,7 @@ void receive_can_chademo_battery(CAN_frame_t rx_frame) {
break;
}
}
void send_can_chademo_battery() {
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100) {
@ -220,3 +207,11 @@ void send_can_chademo_battery() {
}
}
}
void setup_battery(void) { // Performs one time setup at startup
Serial.println("Chademo battery selected");
system_max_design_voltage_dV = 4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 2000; // 200.0V under this, discharging further is disabled
}
#endif

View file

@ -5,30 +5,30 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define ABSOLUTE_MAX_VOLTAGE \
4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
#define BATTERY_SELECTED
// These parameters need to be mapped
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint8_t bms_status;
extern uint8_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
// These parameters need to be mapped for the inverter
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
void update_values_chademo_battery();
void receive_can_chademo_battery(CAN_frame_t rx_frame);
void send_can_chademo_battery();
void setup_battery(void);
#endif

View file

@ -1,14 +1,14 @@
#include "IMIEV-CZERO-ION-BATTERY.h"
#include "BATTERIES.h"
#ifdef IMIEV_CZERO_ION_BATTERY
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "IMIEV-CZERO-ION-BATTERY.h"
//Code still work in progress, TODO:
//Figure out if CAN messages need to be sent to keep the system happy?
/* Do not change code below unless you are sure what you are doing */
#define BMU_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to inverter
#define BMU_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to inverter
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
static uint8_t BMU_Detected = 0;
@ -40,33 +40,31 @@ static double min_volt_cel = 3.70;
static double max_temp_cel = 20.00;
static double min_temp_cel = 19.00;
void update_values_imiev_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
bms_status = ACTIVE; //Startout in active mode
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
system_real_SOC_pptt = (uint16_t)(BMU_SOC * 100); //increase BMU_SOC range from 0-100 -> 100.00
SOC = (uint16_t)(BMU_SOC * 100); //increase BMU_SOC range from 0-100 -> 100.00
system_battery_voltage_dV = (uint16_t)(BMU_PackVoltage * 10); // Multiply by 10 and cast to uint16_t
battery_voltage = (uint16_t)(BMU_PackVoltage * 10); // Multiply by 10 and cast to uint16_t
system_battery_current_dA = (BMU_Current * 10); //Todo, scaling?
battery_current = (BMU_Current * 10); //Todo, scaling?
system_capacity_Wh = BATTERY_WH_MAX; //Hardcoded to header value
capacity_Wh = BATTERY_WH_MAX; //Hardcoded to header value
remaining_capacity_Wh = (uint16_t)((SOC / 10000) * capacity_Wh);
system_remaining_capacity_Wh = (uint16_t)((system_real_SOC_pptt / 10000) * system_capacity_Wh);
//We do not know if the max charge power is sent by the battery. So we estimate the value based on SOC%
if (SOC == 10000) { //100.00%
max_target_charge_power = 0; //When battery is 100% full, set allowed charge W to 0
if (system_scaled_SOC_pptt == 10000) { //100.00%
system_max_charge_power_W = 0; //When battery is 100% full, set allowed charge W to 0
} else {
max_target_charge_power = 10000; //Otherwise we can push 10kW into the pack!
system_max_charge_power_W = 10000; //Otherwise we can push 10kW into the pack!
}
if (SOC < 200) { //2.00%
max_target_discharge_power = 0; //When battery is empty (below 2%), set allowed discharge W to 0
if (system_scaled_SOC_pptt < 200) { //2.00%
system_max_discharge_power_W = 0; //When battery is empty (below 2%), set allowed discharge W to 0
} else {
max_target_discharge_power = 10000; //Otherwise we can discharge 10kW from the pack!
system_max_discharge_power_W = 10000; //Otherwise we can discharge 10kW from the pack!
}
stat_batt_power = BMU_Power; //TODO: Scaling?
system_active_power_W = BMU_Power; //TODO: Scaling?
static int n = sizeof(cell_voltages) / sizeof(cell_voltages[0]);
max_volt_cel = cell_voltages[0]; // Initialize max with the first element of the array
@ -98,21 +96,20 @@ void update_values_imiev_battery() { //This function maps all the values fetche
}
}
cell_max_voltage = (uint16_t)(max_volt_cel * 1000);
system_cell_max_voltage_mV = (uint16_t)(max_volt_cel * 1000);
cell_min_voltage = (uint16_t)(min_volt_cel * 1000);
system_cell_min_voltage_mV = (uint16_t)(min_volt_cel * 1000);
temperature_min = (uint16_t)(min_temp_cel * 1000);
system_temperature_min_dC = (int16_t)(min_temp_cel * 1000);
temperature_max = (uint16_t)(max_temp_cel * 1000);
system_temperature_min_dC = (int16_t)(max_temp_cel * 1000);
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
bms_status = FAULT;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
set_event(EVENT_CAN_FAILURE, 0);
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (!BMU_Detected) {
@ -141,30 +138,30 @@ void update_values_imiev_battery() { //This function maps all the values fetche
Serial.println("Values sent to inverter");
Serial.print("SOC% (0-100.00): ");
Serial.print(SOC);
Serial.print(system_scaled_SOC_pptt);
Serial.print(" Voltage (0-400.0): ");
Serial.print(battery_voltage);
Serial.print(system_battery_voltage_dV);
Serial.print(" Capacity WH full (0-60000): ");
Serial.print(capacity_Wh);
Serial.print(system_capacity_Wh);
Serial.print(" Capacity WH remain (0-60000): ");
Serial.print(remaining_capacity_Wh);
Serial.print(system_remaining_capacity_Wh);
Serial.print(" Max charge power W (0-10000): ");
Serial.print(max_target_charge_power);
Serial.print(system_max_charge_power_W);
Serial.print(" Max discharge power W (0-10000): ");
Serial.print(max_target_discharge_power);
Serial.print(system_max_discharge_power_W);
Serial.print(" Temp max ");
Serial.print(temperature_max);
Serial.print(system_temperature_max_dC);
Serial.print(" Temp min ");
Serial.print(temperature_min);
Serial.print(system_temperature_min_dC);
Serial.print(" Cell mV max ");
Serial.print(cell_max_voltage);
Serial.print(system_cell_max_voltage_mV);
Serial.print(" Cell mV min ");
Serial.print(cell_min_voltage);
Serial.print(system_cell_min_voltage_mV);
#endif
}
void receive_can_imiev_battery(CAN_frame_t rx_frame) {
void receive_can_battery(CAN_frame_t rx_frame) {
CANstillAlive =
12; //TODO: move this inside a known message ID to prevent CAN inverter from keeping battery alive detection going
switch (rx_frame.MsgID) {
@ -220,10 +217,19 @@ void receive_can_imiev_battery(CAN_frame_t rx_frame) {
}
}
void send_can_imiev_battery() {
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100) {
previousMillis100 = currentMillis;
}
}
void setup_battery(void) { // Performs one time setup at startup
Serial.println("Mitsubishi i-MiEV / Citroen C-Zero / Peugeot Ion battery selected");
system_max_design_voltage_dV = 4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
}
#endif

View file

@ -5,33 +5,31 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define ABSOLUTE_MAX_VOLTAGE \
4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
#define BATTERY_SELECTED
// These parameters need to be mapped for the Gen24
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint8_t bms_status;
extern uint8_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern uint16_t cell_max_voltage;
extern uint16_t cell_min_voltage;
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint8_t LEDcolor;
// These parameters need to be mapped for the inverter
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
void update_values_imiev_battery();
void receive_can_imiev_battery(CAN_frame_t rx_frame);
void send_can_imiev_battery();
void setup_battery(void);
#endif

View file

@ -1,7 +1,9 @@
#include "KIA-HYUNDAI-64-BATTERY.h"
#include "BATTERIES.h"
#ifdef KIA_HYUNDAI_64_BATTERY
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "KIA-HYUNDAI-64-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis100 = 0; // will store last time a 100ms CAN Message was send
@ -10,8 +12,6 @@ static const int interval100 = 100; // interval (ms) at which send CAN
static const int interval10ms = 10; // interval (ms) at which send CAN Messages
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
#define MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Inverter
#define MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Inverter
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2950 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION 150 //LED turns yellow on the board if mv delta exceeds this value
@ -146,100 +146,80 @@ CAN_frame_t KIA64_7E4_ack = {
.MsgID = 0x7E4,
.data = {0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; //Ack frame, correct PID is returned
void update_values_kiaHyundai_64_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
//Calculate the SOC% value to send to inverter
soc_calculated = SOC_Display;
soc_calculated = MIN_SOC + (MAX_SOC - MIN_SOC) * (soc_calculated - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
if (soc_calculated < 0) { //We are in the real SOC% range of 0-20%, always set SOC sent to Inverter as 0%
soc_calculated = 0;
}
if (soc_calculated > 1000) { //We are in the real SOC% range of 80-100%, always set SOC sent to Inverter as 100%
soc_calculated = 1000;
}
SOC = (soc_calculated * 10); //increase SOC range from 0-100.0 -> 100.00
system_real_SOC_pptt = (SOC_Display * 10); //increase SOC range from 0-100.0 -> 100.00
StateOfHealth = (batterySOH * 10); //Increase decimals from 100.0% -> 100.00%
system_SOH_pptt = (batterySOH * 10); //Increase decimals from 100.0% -> 100.00%
battery_voltage = batteryVoltage; //value is *10 (3700 = 370.0)
system_battery_voltage_dV = batteryVoltage; //value is *10 (3700 = 370.0)
battery_current = convertToUnsignedInt16(batteryAmps); //value is *10 (150 = 15.0)
system_battery_current_dA = batteryAmps; //value is *10 (150 = 15.0)
capacity_Wh = BATTERY_WH_MAX;
system_capacity_Wh = BATTERY_WH_MAX;
remaining_capacity_Wh = static_cast<int>((static_cast<double>(SOC) / 10000) * BATTERY_WH_MAX);
system_remaining_capacity_Wh = static_cast<int>((static_cast<double>(system_real_SOC_pptt) / 10000) * BATTERY_WH_MAX);
//max_target_charge_power = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts
//system_max_charge_power_W = (uint16_t)allowedChargePower * 10; //From kW*100 to Watts
//The allowed charge power is not available. We estimate this value
if (SOC == 10000) { // When scaled SOC is 100%, set allowed charge power to 0
max_target_charge_power = 0;
if (system_scaled_SOC_pptt == 10000) { // When scaled SOC is 100%, set allowed charge power to 0
system_max_charge_power_W = 0;
} else { // No limits, max charging power allowed
max_target_charge_power = MAXCHARGEPOWERALLOWED;
system_max_charge_power_W = MAXCHARGEPOWERALLOWED;
}
//max_target_discharge_power = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
if (SOC < 100) { // When scaled SOC is <1%, set allowed charge power to 0
max_target_discharge_power = 0;
//system_max_discharge_power_W = (uint16_t)allowedDischargePower * 10; //From kW*100 to Watts
if (system_scaled_SOC_pptt < 100) { // When scaled SOC is <1%, set allowed charge power to 0
system_max_discharge_power_W = 0;
} else { // No limits, max charging power allowed
max_target_discharge_power = MAXDISCHARGEPOWERALLOWED;
system_max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
}
powerWatt = ((batteryVoltage * batteryAmps) / 100);
stat_batt_power = convertToUnsignedInt16(powerWatt); //Power in watts, Negative = charging batt
system_active_power_W = powerWatt; //Power in watts, Negative = charging batt
temperature_min = convertToUnsignedInt16((int8_t)temperatureMin * 10); //Increase decimals, 17C -> 17.0C
system_temperature_min_dC = (int8_t)temperatureMin * 10; //Increase decimals, 17C -> 17.0C
temperature_max = convertToUnsignedInt16((int8_t)temperatureMax * 10); //Increase decimals, 18C -> 18.0C
system_temperature_max_dC = (int8_t)temperatureMax * 10; //Increase decimals, 18C -> 18.0C
cell_max_voltage = CellVoltMax_mV;
system_cell_max_voltage_mV = CellVoltMax_mV;
cell_min_voltage = CellVoltMin_mV;
bms_status = ACTIVE; //Startout in active mode. Then check safeties
system_cell_min_voltage_mV = CellVoltMin_mV;
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
bms_status = FAULT;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
set_event(EVENT_CAN_FAILURE, 0);
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (waterleakageSensor == 0) {
Serial.println("Water leakage inside battery detected. Operation halted. Inspect battery!");
bms_status = FAULT;
set_event(EVENT_WATER_INGRESS, 0);
}
if (leadAcidBatteryVoltage < 110) {
Serial.println("12V battery source below required voltage to safely close contactors. Inspect the supply/battery!");
LEDcolor = YELLOW;
set_event(EVENT_12V_LOW, leadAcidBatteryVoltage);
}
// Check if cell voltages are within allowed range
cell_deviation_mV = (cell_max_voltage - cell_min_voltage);
cell_deviation_mV = (system_cell_max_voltage_mV - system_cell_min_voltage_mV);
if (cell_max_voltage >= MAX_CELL_VOLTAGE) {
bms_status = FAULT;
Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
if (CellVoltMax_mV >= MAX_CELL_VOLTAGE) {
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (cell_min_voltage <= MIN_CELL_VOLTAGE) {
bms_status = FAULT;
Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
if (CellVoltMin_mV <= MIN_CELL_VOLTAGE) {
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
LEDcolor = YELLOW;
Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!");
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
if (bms_status == FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
max_target_charge_power = 0;
max_target_discharge_power = 0;
if (system_bms_status == FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
system_max_charge_power_W = 0;
system_max_discharge_power_W = 0;
}
/* Safeties verified. Perform USB serial printout if configured to do so */
@ -258,7 +238,7 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value
Serial.print(" Amps | ");
Serial.print((uint16_t)batteryVoltage / 10.0, 1);
Serial.print(" Volts | ");
Serial.print((int16_t)stat_batt_power);
Serial.print((int16_t)system_active_power_W);
Serial.println(" Watts");
Serial.print("Allowed Charge ");
Serial.print((uint16_t)allowedChargePower * 10);
@ -304,7 +284,7 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value
#endif
}
void receive_can_kiaHyundai_64_battery(CAN_frame_t rx_frame) {
void receive_can_battery(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0x4DE:
break;
@ -373,53 +353,53 @@ void receive_can_kiaHyundai_64_battery(CAN_frame_t rx_frame) {
allowedDischargePower = ((rx_frame.data.u8[5] << 8) + rx_frame.data.u8[6]);
batteryRelay = rx_frame.data.u8[7];
} else if (poll_data_pid == 2) {
cellvoltages[0] = (rx_frame.data.u8[2] * 20);
cellvoltages[1] = (rx_frame.data.u8[3] * 20);
cellvoltages[2] = (rx_frame.data.u8[4] * 20);
cellvoltages[3] = (rx_frame.data.u8[5] * 20);
cellvoltages[4] = (rx_frame.data.u8[6] * 20);
cellvoltages[5] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[0] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[1] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[2] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[3] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[4] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[5] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
cellvoltages[32] = (rx_frame.data.u8[2] * 20);
cellvoltages[33] = (rx_frame.data.u8[3] * 20);
cellvoltages[34] = (rx_frame.data.u8[4] * 20);
cellvoltages[35] = (rx_frame.data.u8[5] * 20);
cellvoltages[36] = (rx_frame.data.u8[6] * 20);
cellvoltages[37] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[32] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[33] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[34] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[35] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[36] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[37] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
cellvoltages[64] = (rx_frame.data.u8[2] * 20);
cellvoltages[65] = (rx_frame.data.u8[3] * 20);
cellvoltages[66] = (rx_frame.data.u8[4] * 20);
cellvoltages[67] = (rx_frame.data.u8[5] * 20);
cellvoltages[68] = (rx_frame.data.u8[6] * 20);
cellvoltages[69] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[64] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[65] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[66] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[67] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[68] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[69] = (rx_frame.data.u8[7] * 20);
}
break;
case 0x22: //Second datarow in PID group
if (poll_data_pid == 2) {
cellvoltages[6] = (rx_frame.data.u8[1] * 20);
cellvoltages[7] = (rx_frame.data.u8[2] * 20);
cellvoltages[8] = (rx_frame.data.u8[3] * 20);
cellvoltages[9] = (rx_frame.data.u8[4] * 20);
cellvoltages[10] = (rx_frame.data.u8[5] * 20);
cellvoltages[11] = (rx_frame.data.u8[6] * 20);
cellvoltages[12] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[6] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[7] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[8] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[9] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[10] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[11] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[12] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
cellvoltages[38] = (rx_frame.data.u8[1] * 20);
cellvoltages[39] = (rx_frame.data.u8[2] * 20);
cellvoltages[40] = (rx_frame.data.u8[3] * 20);
cellvoltages[41] = (rx_frame.data.u8[4] * 20);
cellvoltages[42] = (rx_frame.data.u8[5] * 20);
cellvoltages[43] = (rx_frame.data.u8[6] * 20);
cellvoltages[44] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[38] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[39] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[40] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[41] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[42] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[43] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[44] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
cellvoltages[70] = (rx_frame.data.u8[1] * 20);
cellvoltages[71] = (rx_frame.data.u8[2] * 20);
cellvoltages[72] = (rx_frame.data.u8[3] * 20);
cellvoltages[73] = (rx_frame.data.u8[4] * 20);
cellvoltages[74] = (rx_frame.data.u8[5] * 20);
cellvoltages[75] = (rx_frame.data.u8[6] * 20);
cellvoltages[76] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[70] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[71] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[72] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[73] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[74] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[75] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[76] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 6) {
batteryManagementMode = rx_frame.data.u8[5];
}
@ -429,29 +409,29 @@ void receive_can_kiaHyundai_64_battery(CAN_frame_t rx_frame) {
temperature_water_inlet = rx_frame.data.u8[6];
CellVoltMax_mV = (rx_frame.data.u8[7] * 20); //(volts *50) *20 =mV
} else if (poll_data_pid == 2) {
cellvoltages[13] = (rx_frame.data.u8[1] * 20);
cellvoltages[14] = (rx_frame.data.u8[2] * 20);
cellvoltages[15] = (rx_frame.data.u8[3] * 20);
cellvoltages[16] = (rx_frame.data.u8[4] * 20);
cellvoltages[17] = (rx_frame.data.u8[5] * 20);
cellvoltages[18] = (rx_frame.data.u8[6] * 20);
cellvoltages[19] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[13] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[14] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[15] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[16] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[17] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[18] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[19] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
cellvoltages[45] = (rx_frame.data.u8[1] * 20);
cellvoltages[46] = (rx_frame.data.u8[2] * 20);
cellvoltages[47] = (rx_frame.data.u8[3] * 20);
cellvoltages[48] = (rx_frame.data.u8[4] * 20);
cellvoltages[49] = (rx_frame.data.u8[5] * 20);
cellvoltages[50] = (rx_frame.data.u8[6] * 20);
cellvoltages[51] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[45] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[46] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[47] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[48] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[49] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[50] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[51] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
cellvoltages[77] = (rx_frame.data.u8[1] * 20);
cellvoltages[78] = (rx_frame.data.u8[2] * 20);
cellvoltages[79] = (rx_frame.data.u8[3] * 20);
cellvoltages[80] = (rx_frame.data.u8[4] * 20);
cellvoltages[81] = (rx_frame.data.u8[5] * 20);
cellvoltages[82] = (rx_frame.data.u8[6] * 20);
cellvoltages[83] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[77] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[78] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[79] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[80] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[81] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[82] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[83] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
heatertemp = rx_frame.data.u8[7];
}
@ -462,56 +442,56 @@ void receive_can_kiaHyundai_64_battery(CAN_frame_t rx_frame) {
CellVminNo = rx_frame.data.u8[3];
CellVoltMin_mV = (rx_frame.data.u8[2] * 20); //(volts *50) *20 =mV
} else if (poll_data_pid == 2) {
cellvoltages[20] = (rx_frame.data.u8[1] * 20);
cellvoltages[21] = (rx_frame.data.u8[2] * 20);
cellvoltages[22] = (rx_frame.data.u8[3] * 20);
cellvoltages[23] = (rx_frame.data.u8[4] * 20);
cellvoltages[24] = (rx_frame.data.u8[5] * 20);
cellvoltages[25] = (rx_frame.data.u8[6] * 20);
cellvoltages[26] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[20] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[21] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[22] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[23] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[24] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[25] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[26] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 3) {
cellvoltages[52] = (rx_frame.data.u8[1] * 20);
cellvoltages[53] = (rx_frame.data.u8[2] * 20);
cellvoltages[54] = (rx_frame.data.u8[3] * 20);
cellvoltages[55] = (rx_frame.data.u8[4] * 20);
cellvoltages[56] = (rx_frame.data.u8[5] * 20);
cellvoltages[57] = (rx_frame.data.u8[6] * 20);
cellvoltages[58] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[52] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[53] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[54] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[55] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[56] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[57] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[58] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 4) {
cellvoltages[84] = (rx_frame.data.u8[1] * 20);
cellvoltages[85] = (rx_frame.data.u8[2] * 20);
cellvoltages[86] = (rx_frame.data.u8[3] * 20);
cellvoltages[87] = (rx_frame.data.u8[4] * 20);
cellvoltages[88] = (rx_frame.data.u8[5] * 20);
cellvoltages[89] = (rx_frame.data.u8[6] * 20);
cellvoltages[90] = (rx_frame.data.u8[7] * 20);
system_cellvoltages_mV[84] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[85] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[86] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[87] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[88] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[89] = (rx_frame.data.u8[6] * 20);
system_cellvoltages_mV[90] = (rx_frame.data.u8[7] * 20);
} else if (poll_data_pid == 5) {
batterySOH = ((rx_frame.data.u8[2] << 8) + rx_frame.data.u8[3]);
}
break;
case 0x25: //Fifth datarow in PID group
if (poll_data_pid == 2) {
cellvoltages[27] = (rx_frame.data.u8[1] * 20);
cellvoltages[28] = (rx_frame.data.u8[2] * 20);
cellvoltages[29] = (rx_frame.data.u8[3] * 20);
cellvoltages[30] = (rx_frame.data.u8[4] * 20);
cellvoltages[31] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[27] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[28] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[29] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[30] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[31] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 3) {
cellvoltages[59] = (rx_frame.data.u8[1] * 20);
cellvoltages[60] = (rx_frame.data.u8[2] * 20);
cellvoltages[61] = (rx_frame.data.u8[3] * 20);
cellvoltages[62] = (rx_frame.data.u8[4] * 20);
cellvoltages[63] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[59] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[60] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[61] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[62] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[63] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 4) {
cellvoltages[91] = (rx_frame.data.u8[1] * 20);
cellvoltages[92] = (rx_frame.data.u8[2] * 20);
cellvoltages[93] = (rx_frame.data.u8[3] * 20);
cellvoltages[94] = (rx_frame.data.u8[4] * 20);
cellvoltages[95] = (rx_frame.data.u8[5] * 20);
system_cellvoltages_mV[91] = (rx_frame.data.u8[1] * 20);
system_cellvoltages_mV[92] = (rx_frame.data.u8[2] * 20);
system_cellvoltages_mV[93] = (rx_frame.data.u8[3] * 20);
system_cellvoltages_mV[94] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[95] = (rx_frame.data.u8[5] * 20);
} else if (poll_data_pid == 5) {
cellvoltages[96] = (rx_frame.data.u8[4] * 20);
cellvoltages[97] = (rx_frame.data.u8[5] * 20);
nof_cellvoltages = 98;
system_cellvoltages_mV[96] = (rx_frame.data.u8[4] * 20);
system_cellvoltages_mV[97] = (rx_frame.data.u8[5] * 20);
system_number_of_cells = 98;
}
break;
case 0x26: //Sixth datarow in PID group
@ -534,7 +514,7 @@ void receive_can_kiaHyundai_64_battery(CAN_frame_t rx_frame) {
}
}
void send_can_kiaHyundai_64_battery() {
void send_can_battery() {
unsigned long currentMillis = millis();
//Send 100ms message
if (currentMillis - previousMillis100 >= interval100) {
@ -600,10 +580,11 @@ void send_can_kiaHyundai_64_battery() {
}
}
uint16_t convertToUnsignedInt16(int16_t signed_value) {
if (signed_value < 0) {
return (65535 + signed_value);
} else {
return (uint16_t)signed_value;
}
void setup_battery(void) { // Performs one time setup at startup
Serial.println("Kia Niro / Hyundai Kona 64kWh battery selected");
system_max_design_voltage_dV = 4040; // 404.0V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
}
#endif

View file

@ -5,37 +5,34 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define ABSOLUTE_MAX_VOLTAGE \
4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
#define BATTERY_SELECTED
#define MAXCHARGEPOWERALLOWED 10000
#define MAXDISCHARGEPOWERALLOWED 10000
// These parameters need to be mapped for the Gen24
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern uint8_t nof_cellvoltages; // Total number of cell voltages, set by each battery.
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
// These parameters need to be mapped for the inverter
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
void update_values_kiaHyundai_64_battery();
void receive_can_kiaHyundai_64_battery(CAN_frame_t rx_frame);
void send_can_kiaHyundai_64_battery();
uint16_t convertToUnsignedInt16(int16_t signed_value);
void setup_battery(void);
#endif

View file

@ -1,3 +1,5 @@
#include "BATTERIES.h"
#ifdef NISSAN_LEAF_BATTERY
#include "NISSAN-LEAF-BATTERY.h"
#ifdef MQTT
#include "../devboard/mqtt/mqtt.h"
@ -91,20 +93,17 @@ static uint8_t crctable[256] = {
#define AZE0_BATTERY 1
#define ZE1_BATTERY 2
static uint8_t LEAF_Battery_Type = ZE0_BATTERY;
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
#define WH_PER_GID 77 //One GID is this amount of Watt hours
#define LB_MAX_SOC 1000 //LEAF BMS never goes over this value. We use this info to rescale SOC% sent to Fronius
#define LB_MIN_SOC 0 //LEAF BMS never goes below this value. We use this info to rescale SOC% sent to Fronius
#define MAX_CELL_VOLTAGE 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE 2700 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION 500 //LED turns yellow on the board if mv delta exceeds this value
#define WH_PER_GID 77 //One GID is this amount of Watt hours
static uint16_t LB_Discharge_Power_Limit = 0; //Limit in kW
static uint16_t LB_Charge_Power_Limit = 0; //Limit in kW
static int16_t LB_MAX_POWER_FOR_CHARGER = 0; //Limit in kW
static int16_t LB_SOC = 500; //0 - 100.0 % (0-1000) The real SOC% in the battery
static int16_t CalculatedSOC = 0; // Temporary value used for calculating SOC
static uint16_t LB_TEMP = 0; //Temporary value used in status checks
static uint16_t LB_Wh_Remaining = 0; //Amount of energy in battery, in Wh
static uint16_t LB_GIDS = 0;
static uint16_t LB_GIDS = 273; //Startup in 24kWh mode
static uint16_t LB_MAX = 0;
static uint16_t LB_Max_GIDS = 273; //Startup in 24kWh mode
static uint16_t LB_StateOfHealth = 99; //State of health %
@ -165,114 +164,106 @@ void print_with_units(char* header, int value, char* units) {
Serial.print(units);
}
void update_values_leaf_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 */
/* Start with mapping all values */
StateOfHealth = (LB_StateOfHealth * 100); //Increase range from 99% -> 99.00%
system_SOH_pptt = (LB_StateOfHealth * 100); //Increase range from 99% -> 99.00%
//Calculate the SOC% value to send to Fronius
CalculatedSOC = LB_SOC;
CalculatedSOC =
LB_MIN_SOC + (LB_MAX_SOC - LB_MIN_SOC) * (CalculatedSOC - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
if (CalculatedSOC < 0) { //We are in the real SOC% range of 0-20%, always set SOC sent to Fronius as 0%
CalculatedSOC = 0;
}
if (CalculatedSOC > 1000) { //We are in the real SOC% range of 80-100%, always set SOC sent to Fronius as 100%
CalculatedSOC = 1000;
}
SOC = (CalculatedSOC * 10); //increase CalculatedSOC range from 0-100.0 -> 100.00
system_real_SOC_pptt = (LB_SOC * 10);
battery_voltage = (LB_Total_Voltage2 * 5); //0.5V /bit, multiply by 5 to get Voltage+1decimal (350.5V = 701)
system_battery_voltage_dV = (LB_Total_Voltage2 * 5); //0.5V/bit, multiply by 5 to get Voltage+1decimal (350.5V = 701)
battery_current = convert2unsignedint16((LB_Current2 * 5)); //0.5A/bit, multiply by 5 to get Amp+1decimal (5,5A = 11)
system_battery_current_dA = (LB_Current2 * 5); //0.5A/bit, multiply by 5 to get Amp+1decimal (5,5A = 11)
capacity_Wh = (LB_Max_GIDS * WH_PER_GID);
system_capacity_Wh = (LB_Max_GIDS * WH_PER_GID);
remaining_capacity_Wh = LB_Wh_Remaining;
system_remaining_capacity_Wh = LB_Wh_Remaining;
LB_Power =
((LB_Total_Voltage2 * LB_Current2) / 4); //P = U * I (Both values are 0.5 per bit so the math is non-intuitive)
stat_batt_power = convert2unsignedint16(LB_Power); //add sign if needed
system_active_power_W = LB_Power;
//Update temperature readings. Method depends on which generation LEAF battery is used
if (LEAF_Battery_Type == ZE0_BATTERY) {
//Since we only have average value, send the minimum as -1.0 degrees below average
temperature_min =
convert2unsignedint16((LB_AverageTemperature * 10) - 10); //add sign if negative and increase range
temperature_max = convert2unsignedint16((LB_AverageTemperature * 10));
system_temperature_min_dC = ((LB_AverageTemperature * 10) - 10); //Increase range from C to C+1, remove 1.0C
system_temperature_max_dC = (LB_AverageTemperature * 10); //Increase range from C to C+1
} else if (LEAF_Battery_Type == AZE0_BATTERY) {
//Use the value sent constantly via CAN in 5C0 (only available on AZE0)
temperature_min =
convert2unsignedint16((LB_HistData_Temperature_MIN * 10)); //add sign if negative and increase range
temperature_max = convert2unsignedint16((LB_HistData_Temperature_MAX * 10));
system_temperature_min_dC = (LB_HistData_Temperature_MIN * 10); //Increase range from C to C+1
system_temperature_max_dC = (LB_HistData_Temperature_MAX * 10); //Increase range from C to C+1
} else { // ZE1 (TODO: Once the muxed value in 5C0 becomes known, switch to using that instead of this complicated polled value)
if (temp_raw_min != 0) //We have a polled value available
{
temp_polled_min = ((Temp_fromRAW_to_F(temp_raw_min) - 320) * 5) / 9; //Convert from F to C
temp_polled_max = ((Temp_fromRAW_to_F(temp_raw_max) - 320) * 5) / 9; //Convert from F to C
if (temp_polled_min < temp_polled_max) { //Catch any edge cases from Temp_fromRAW_to_F function
temperature_min = convert2unsignedint16((temp_polled_min));
temperature_max = convert2unsignedint16((temp_polled_max));
system_temperature_min_dC = temp_polled_min;
system_temperature_max_dC = temp_polled_max;
} else {
temperature_min = convert2unsignedint16((temp_polled_max));
temperature_max = convert2unsignedint16((temp_polled_min));
system_temperature_min_dC = temp_polled_max;
system_temperature_max_dC = temp_polled_min;
}
}
}
// Define power able to be discharged from battery
if (LB_Discharge_Power_Limit > 30) { //if >30kW can be pulled from battery
max_target_discharge_power = 30000; //cap value so we don't go over the Fronius limits
if (LB_Discharge_Power_Limit > 60) { //if >60kW can be pulled from battery
system_max_discharge_power_W = 60000; //cap value so we don't go over uint16 value
} else {
max_target_discharge_power = (LB_Discharge_Power_Limit * 1000); //kW to W
system_max_discharge_power_W = (LB_Discharge_Power_Limit * 1000); //kW to W
}
if (SOC == 0) { //Scaled SOC% value is 0.00%, we should not discharge battery further
max_target_discharge_power = 0;
if (system_scaled_SOC_pptt == 0) { //Scaled SOC% value is 0.00%, we should not discharge battery further
system_max_discharge_power_W = 0;
}
// Define power able to be put into the battery
if (LB_Charge_Power_Limit > 30) { //if >30kW can be put into the battery
max_target_charge_power = 30000; //cap value so we don't go over the Fronius limits
if (LB_Charge_Power_Limit > 60) { //if >60kW can be put into the battery
system_max_charge_power_W = 60000; //cap value so we don't go over uint16 value
} else {
max_target_charge_power = (LB_Charge_Power_Limit * 1000); //kW to W
system_max_charge_power_W = (LB_Charge_Power_Limit * 1000); //kW to W
}
if (SOC == 10000) //Scaled SOC% value is 100.00%
if (system_scaled_SOC_pptt == 10000) //Scaled SOC% value is 100.00%
{
max_target_charge_power = 0; //No need to charge further, set max power to 0
system_max_charge_power_W = 0; //No need to charge further, set max power to 0
}
//Map all cell voltages to the global array
for (int i = 0; i < 96; ++i) {
cellvoltages[i] = cell_voltages[i];
system_cellvoltages_mV[i] = cell_voltages[i];
}
bms_status = ACTIVE; //Startout in active mode
/*Extra safety functions below*/
if (LB_GIDS < 10) //800Wh left in battery
if (LB_GIDS < 10) //700Wh left in battery!
{ //Battery is running abnormally low, some discharge logic might have failed. Zero it all out.
SOC = 0;
max_target_discharge_power = 0;
set_event(EVENT_BATTERY_EMPTY, 0);
system_real_SOC_pptt = 0;
system_max_discharge_power_W = 0;
}
//Check if SOC% is plausible
if (battery_voltage >
(ABSOLUTE_MAX_VOLTAGE - 100)) { // When pack voltage is close to max, and SOC% is still low, raise FAULT
if (system_battery_voltage_dV >
(system_max_design_voltage_dV - 100)) { // When pack voltage is close to max, and SOC% is still low, raise FAULT
if (LB_SOC < 650) {
bms_status = FAULT;
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!");
#endif
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, LB_SOC / 10);
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, LB_SOC / 10); // Set event with the SOC as data
} else {
clear_event(EVENT_SOC_PLAUSIBILITY_ERROR);
}
}
if (LB_Full_CHARGE_flag) { //Battery reports that it is fully charged stop all further charging incase it hasn't already
max_target_charge_power = 0;
set_event(EVENT_BATTERY_FULL, 0);
system_max_charge_power_W = 0;
} else {
clear_event(EVENT_BATTERY_FULL);
}
if (LB_Capacity_Empty) { //Battery reports that it is fully discharged. Stop all further discharging incase it hasn't already
max_target_discharge_power = 0;
set_event(EVENT_BATTERY_EMPTY, 0);
system_max_discharge_power_W = 0;
} else {
clear_event(EVENT_BATTERY_EMPTY);
}
if (LB_Relay_Cut_Request) { //LB_FAIL, BMS requesting shutdown and contactors to be opened
@ -281,8 +272,8 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
#endif
//Note, this is sometimes triggered during the night while idle, and the BMS recovers after a while. Removed latching from this scenario
errorCode = 1;
max_target_discharge_power = 0;
max_target_charge_power = 0;
system_max_discharge_power_W = 0;
system_max_charge_power_W = 0;
}
if (LB_Failsafe_Status > 0) // 0 is normal, start charging/discharging
@ -308,87 +299,64 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
break;
case (5):
//Caution Lamp Request & Normal Stop Request
bms_status = FAULT;
errorCode = 2;
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!");
#endif
set_event(EVENT_BATTERY_DISCHG_STOP_REQ, 0);
break;
case (6):
//Caution Lamp Request & Charging Mode Stop Request
bms_status = FAULT;
errorCode = 3;
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!");
#endif
set_event(EVENT_BATTERY_CHG_STOP_REQ, 0);
break;
case (7):
//Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request
bms_status = FAULT;
errorCode = 4;
#ifdef DEBUG_VIA_USB
Serial.println(
"ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!");
#endif
set_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ, 0);
break;
default:
break;
}
} else { //LB_Failsafe_Status == 0
clear_event(EVENT_BATTERY_DISCHG_STOP_REQ);
clear_event(EVENT_BATTERY_CHG_STOP_REQ);
clear_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ);
}
if (LB_StateOfHealth < 25) { //Battery is extremely degraded, not fit for secondlifestorage. Zero it all out.
if (LB_StateOfHealth != 0) { //Extra check to see that we actually have a SOH Value available
#ifdef DEBUG_VIA_USB
Serial.println(
"ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery.");
#endif
bms_status = FAULT;
errorCode = 5;
set_event(EVENT_LOW_SOH, LB_StateOfHealth);
max_target_discharge_power = 0;
max_target_charge_power = 0;
} else {
clear_event(EVENT_LOW_SOH);
}
}
#ifdef INTERLOCK_REQUIRED
if (!LB_Interlock) {
#ifdef DEBUG_VIA_USB
Serial.println(
"ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be "
"disabled!");
#endif
bms_status = FAULT;
set_event(EVENT_HVIL_FAILURE, 0);
errorCode = 6;
SOC = 0;
max_target_discharge_power = 0;
max_target_charge_power = 0;
} else {
clear_event(EVENT_HVIL_FAILURE);
}
#endif
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
bms_status = FAULT;
errorCode = 7;
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control.");
#endif
set_event(EVENT_CAN_FAILURE, 0);
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (CANerror >
MAX_CAN_FAILURES) //Also check if we have recieved too many malformed CAN messages. If so, signal via LED
{
errorCode = 10;
LEDcolor = YELLOW;
#ifdef DEBUG_VIA_USB
Serial.println("ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!");
#endif
set_event(EVENT_CAN_WARNING, 0);
set_event(EVENT_CAN_RX_WARNING, 0);
}
if (system_bms_status == FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
system_max_charge_power_W = 0;
system_max_discharge_power_W = 0;
}
/*Finally print out values to serial if configured to do so*/
@ -398,33 +366,20 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
Serial.println(errorCode);
}
Serial.println("Values going to inverter");
print_with_units("SOH%: ", (StateOfHealth * 0.01), "% ");
print_with_units(", SOC% scaled: ", (SOC * 0.01), "% ");
print_with_units(", Voltage: ", (battery_voltage * 0.1), "V ");
print_with_units(", Max discharge power: ", max_target_discharge_power, "W ");
print_with_units(", Max charge power: ", max_target_charge_power, "W ");
print_with_units(", Max temp: ", ((int16_t)temperature_max * 0.1), "°C ");
print_with_units(", Min temp: ", ((int16_t)temperature_min * 0.1), "°C ");
print_with_units("SOH%: ", (system_SOH_pptt * 0.01), "% ");
print_with_units(", SOC% scaled: ", (system_scaled_SOC_pptt * 0.01), "% ");
print_with_units(", Voltage: ", (system_battery_voltage_dV * 0.1), "V ");
print_with_units(", Max discharge power: ", system_max_discharge_power_W, "W ");
print_with_units(", Max charge power: ", system_max_charge_power_W, "W ");
print_with_units(", Max temp: ", (system_temperature_max_dC * 0.1), "°C ");
print_with_units(", Min temp: ", (system_temperature_min_dC * 0.1), "°C ");
Serial.println("");
Serial.print("BMS Status: ");
if (bms_status == 3) {
if (system_bms_status == 3) {
Serial.print("Active, ");
} else {
Serial.print("FAULT, ");
}
switch (bms_char_dis_status) {
case 0:
Serial.print("Idle");
break;
case 1:
Serial.print("Discharging");
break;
case 2:
Serial.print("Charging");
break;
default:
break;
}
print_with_units(", Power: ", LB_Power, "W ");
Serial.println("");
Serial.println("Values from battery");
@ -445,7 +400,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched
#endif
}
void receive_can_leaf_battery(CAN_frame_t rx_frame) {
void receive_can_battery(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0x1DB:
if (is_message_corrupt(rx_frame)) {
@ -616,31 +571,19 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) {
cell_deviation_mV = (min_max_voltage[1] - min_max_voltage[0]);
cell_max_voltage = min_max_voltage[1];
cell_min_voltage = min_max_voltage[0];
system_cell_max_voltage_mV = min_max_voltage[1];
system_cell_min_voltage_mV = min_max_voltage[0];
if (cell_deviation_mV > MAX_CELL_DEVIATION) {
LEDcolor = YELLOW;
#ifdef DEBUG_VIA_USB
Serial.println("HIGH CELL DEVIATION!!! Inspect battery!");
#endif
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
}
if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) {
bms_status = FAULT;
errorCode = 8;
#ifdef DEBUG_VIA_USB
Serial.println("CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
#endif
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (min_max_voltage[0] <= MIN_CELL_VOLTAGE) {
bms_status = FAULT;
errorCode = 9;
#ifdef DEBUG_VIA_USB
Serial.println("CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
#endif
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
break;
@ -721,7 +664,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) {
break;
}
}
void send_can_leaf_battery() {
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100) {
@ -905,14 +848,6 @@ void send_can_leaf_battery() {
}
}
uint16_t convert2unsignedint16(int16_t signed_value) {
if (signed_value < 0) {
return (65535 + signed_value);
} else {
return (uint16_t)signed_value;
}
}
bool is_message_corrupt(CAN_frame_t rx_frame) {
uint8_t crc = 0;
for (uint8_t j = 0; j < 7; j++) {
@ -952,6 +887,12 @@ uint16_t Temp_fromRAW_to_F(uint16_t temperature) { //This function feels horrib
return static_cast<uint16_t>(1094 + (309 - temperature) * 2.5714285714285715);
}
void init_battery(void) {
nof_cellvoltages = 96;
void setup_battery(void) { // Performs one time setup at startup
Serial.println("Nissan LEAF battery selected");
system_number_of_cells = 96;
system_max_design_voltage_dV = 4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 2600; // 260.0V under this, discharging further is disabled
}
#endif

View file

@ -5,38 +5,32 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define ABSOLUTE_MAX_VOLTAGE \
4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
#define BATTERY_SELECTED
// These parameters need to be mapped for the inverter
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint8_t LEDcolor; //Enum, 0-10
extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern uint8_t nof_cellvoltages; // Total number of cell voltages, set by each battery.
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
void update_values_leaf_battery();
void receive_can_leaf_battery(CAN_frame_t rx_frame);
void send_can_leaf_battery();
uint16_t convert2unsignedint16(int16_t signed_value);
uint16_t Temp_fromRAW_to_F(uint16_t temperature);
bool is_message_corrupt(CAN_frame_t rx_frame);
void init_battery(void);
void setup_battery(void);
#endif

View file

@ -1,19 +1,17 @@
#include "RENAULT-KANGOO-BATTERY.h"
#include "BATTERIES.h"
#ifdef RENAULT_KANGOO_BATTERY
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "RENAULT-KANGOO-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
#define LB_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Fronius
#define LB_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Fronius
static uint32_t LB_Battery_Voltage = 3700;
static uint32_t LB_Charge_Power_Limit_Watts = 0;
static uint32_t LB_Discharge_Power_Limit_Watts = 0;
static int32_t LB_Current = 0;
static int16_t LB_MAX_TEMPERATURE = 0;
static int16_t LB_MIN_TEMPERATURE = 0;
static uint16_t soc_calculated = 0;
static uint16_t LB_SOC = 0;
static uint16_t LB_SOH = 0;
static uint16_t LB_Discharge_Power_Limit = 0;
@ -57,115 +55,96 @@ static const int interval10 = 10; // interval (ms) at which send CAN Messag
static const int interval100 = 100; // interval (ms) at which send CAN Messages
static const int interval1000 = 1000; // interval (ms) at which send CAN Messages
void update_values_kangoo_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
bms_status = ACTIVE; //Startout in active mode
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
StateOfHealth = (LB_SOH * 100); //Increase range from 99% -> 99.00%
system_real_SOC_pptt = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
//Calculate the SOC% value to send to Fronius
soc_calculated = LB_SOC;
soc_calculated =
LB_MIN_SOC + (LB_MAX_SOC - LB_MIN_SOC) * (soc_calculated - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
if (soc_calculated < 0) { //We are in the real SOC% range of 0-20%, always set SOC sent to Inverter as 0%
soc_calculated = 0;
}
if (soc_calculated > 1000) { //We are in the real SOC% range of 80-100%, always set SOC sent to Inverter as 100%
soc_calculated = 1000;
}
SOC = (soc_calculated * 10); //increase LB_SOC range from 0-100.0 -> 100.00
system_SOH_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
battery_voltage = LB_Battery_Voltage;
system_battery_voltage_dV = LB_Battery_Voltage;
battery_current = LB_Current;
system_battery_current_dA = LB_Current;
capacity_Wh = BATTERY_WH_MAX; //Hardcoded to header value
system_capacity_Wh = BATTERY_WH_MAX; //Hardcoded to header value
remaining_capacity_Wh = (uint16_t)((SOC / 10000) * capacity_Wh);
system_remaining_capacity_Wh = (uint16_t)((system_real_SOC_pptt / 10000) * system_capacity_Wh);
LB_Discharge_Power_Limit_Watts = (LB_Discharge_Power_Limit * 500); //Convert value fetched from battery to watts
/* Define power able to be discharged from battery */
if (LB_Discharge_Power_Limit_Watts > 30000) //if >30kW can be pulled from battery
if (LB_Discharge_Power_Limit_Watts > 60000) //if >60kW can be pulled from battery
{
max_target_discharge_power = 30000; //cap value so we don't go over the Fronius limits
system_max_discharge_power_W = 60000; //cap value so we don't go over the uint16 limit
} else {
max_target_discharge_power = LB_Discharge_Power_Limit_Watts;
system_max_discharge_power_W = LB_Discharge_Power_Limit_Watts;
}
if (SOC == 0) //Scaled SOC% value is 0.00%, we should not discharge battery further
if (system_scaled_SOC_pptt == 0) //Scaled SOC% value is 0.00%, we should not discharge battery further
{
max_target_discharge_power = 0;
system_max_discharge_power_W = 0;
}
LB_Charge_Power_Limit_Watts = (LB_Charge_Power_Limit * 500); //Convert value fetched from battery to watts
/* Define power able to be put into the battery */
if (LB_Charge_Power_Limit_Watts > 30000) //if >30kW can be put into the battery
if (LB_Charge_Power_Limit_Watts > 60000) //if >60kW can be put into the battery
{
max_target_charge_power = 30000; //cap value so we don't go over the Fronius limits
}
if (LB_Charge_Power_Limit_Watts < 0) {
max_target_charge_power = 0; //cap calue so we dont do under the Fronius limits
system_max_charge_power_W = 60000; //cap value so we don't go over the uint16 limit
} else {
max_target_charge_power = LB_Charge_Power_Limit_Watts;
system_max_charge_power_W = LB_Charge_Power_Limit_Watts;
}
if (SOC == 10000) //Scaled SOC% value is 100.00%
if (system_scaled_SOC_pptt == 10000) //Scaled SOC% value is 100.00%
{
max_target_charge_power = 0; //No need to charge further, set max power to 0
system_max_charge_power_W = 0; //No need to charge further, set max power to 0
}
stat_batt_power = (battery_voltage * LB_Current); //TODO: check if scaling is OK
system_active_power_W = (system_battery_voltage_dV * LB_Current); //TODO: check if scaling is OK
temperature_min = convert2uint16(LB_MIN_TEMPERATURE * 10);
system_temperature_min_dC = (LB_MIN_TEMPERATURE * 10);
temperature_max = convert2uint16(LB_MAX_TEMPERATURE * 10);
system_temperature_max_dC = (LB_MAX_TEMPERATURE * 10);
cell_min_voltage = LB_Cell_Min_Voltage;
system_cell_min_voltage_mV = LB_Cell_Min_Voltage;
cell_max_voltage = LB_Cell_Max_Voltage;
system_cell_max_voltage_mV = LB_Cell_Max_Voltage;
cell_deviation_mV = (cell_max_voltage - cell_min_voltage);
cell_deviation_mV = (system_temperature_max_dC - system_temperature_min_dC);
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
bms_status = FAULT;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
set_event(EVENT_CAN_FAILURE, 0);
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
bms_status = FAULT;
Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
bms_status = FAULT;
Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
LEDcolor = YELLOW;
Serial.println("ERROR: HIGH CELL mV DEVIATION!!! Inspect battery!");
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
#ifdef DEBUG_VIA_USB
Serial.println("Values going to inverter:");
Serial.print("SOH%: ");
Serial.print(StateOfHealth);
Serial.print(system_SOH_pptt);
Serial.print(", SOC% scaled: ");
Serial.print(SOC);
Serial.print(system_scaled_SOC_pptt);
Serial.print(", Voltage: ");
Serial.print(battery_voltage);
Serial.print(system_battery_voltage_dV);
Serial.print(", Max discharge power: ");
Serial.print(max_target_discharge_power);
Serial.print(system_max_discharge_power_W);
Serial.print(", Max charge power: ");
Serial.print(max_target_charge_power);
Serial.print(system_max_charge_power_W);
Serial.print(", Max temp: ");
Serial.print(temperature_max);
Serial.print(system_temperature_max_dC);
Serial.print(", Min temp: ");
Serial.print(temperature_min);
Serial.print(system_temperature_min_dC);
Serial.print(", BMS Status (3=OK): ");
Serial.print(bms_status);
Serial.print(system_bms_status);
Serial.println("Battery values: ");
Serial.print("Real SOC: ");
@ -182,7 +161,7 @@ void update_values_kangoo_battery() { //This function maps all the values fetch
#endif
}
void receive_can_kangoo_battery(CAN_frame_t rx_frame) //GKOE reworked
void receive_can_battery(CAN_frame_t rx_frame) //GKOE reworked
{
switch (rx_frame.MsgID) {
@ -235,7 +214,7 @@ void receive_can_kangoo_battery(CAN_frame_t rx_frame) //GKOE reworked
}
if (rx_frame.data.u8[0] == 0x24) { //5th response Bytes 24-31
LB_Discharge_Power_Limit = word(LB_Discharge_Power_Limit_Byte1, rx_frame.data.u8[1]) * 100; //OK!
LB_Battery_Voltage = word(rx_frame.data.u8[2], rx_frame.data.u8[3]) * 10; //OK!
LB_Battery_Voltage = word(rx_frame.data.u8[2], rx_frame.data.u8[3]) / 10; //OK!
GVB_79B_Continue = false;
}
break;
@ -244,7 +223,7 @@ void receive_can_kangoo_battery(CAN_frame_t rx_frame) //GKOE reworked
}
}
void send_can_kangoo_battery() {
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 100ms CAN Message (for 2.4s, then pause 10s)
if ((currentMillis - previousMillis100) >= (interval100 + GVL_pause)) {
@ -267,10 +246,11 @@ void send_can_kangoo_battery() {
}
}
uint16_t convert2uint16(int16_t signed_value) {
if (signed_value < 0) {
return (65535 + signed_value);
} else {
return (uint16_t)signed_value;
}
void setup_battery(void) { // Performs one time setup at startup
Serial.println("Renault Kangoo battery selected");
system_max_design_voltage_dV = 4040; // 404.0V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
}
#endif

View file

@ -5,39 +5,37 @@
#include "../devboard/config.h" // Needed for defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define ABSOLUTE_MAX_VOLTAGE \
4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
#define BATTERY_SELECTED
#define ABSOLUTE_CELL_MAX_VOLTAGE \
4100 // Max Cell Voltage mV! if voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_CELL_MIN_VOLTAGE \
3000 // Min Cell Voltage mV! if voltage goes under this, discharging further is disabled
#define MAX_CELL_DEVIATION_MV 500 //LED turns yellow on the board if mv delta exceeds this value
// These parameters need to be mapped for the Gen24
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t CANerror;
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
// These parameters need to be mapped for the inverter
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, true/false
void update_values_kangoo_battery();
void receive_can_kangoo_battery(CAN_frame_t rx_frame);
void send_can_kangoo_battery();
uint16_t convert2uint16(int16_t signed_value);
void setup_battery(void);
#endif

View file

@ -1,16 +1,14 @@
#include "RENAULT-ZOE-BATTERY.h"
#include "BATTERIES.h"
#ifdef RENAULT_ZOE_BATTERY
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "RENAULT-ZOE-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
#define LB_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Fronius
#define LB_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Fronius
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
static uint8_t errorCode = 0; //stores if we have an error code active from battery control logic
static uint16_t LB_SOC = 50;
static uint16_t soc_calculated = 0;
static uint16_t LB_SOH = 99;
static int16_t LB_MIN_TEMPERATURE = 0;
static int16_t LB_MAX_TEMPERATURE = 0;
@ -42,91 +40,74 @@ static const int interval10 = 10; // interval (ms) at which send CAN Messag
static const int interval100 = 100; // interval (ms) at which send CAN Messages
static const int interval1000 = 1000; // interval (ms) at which send CAN Messages
void update_values_zoe_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
bms_status = ACTIVE; //Startout in active mode
void update_values_battery() { //This function maps all the values fetched via CAN to the correct parameters used for modbus
system_SOH_pptt = (LB_SOH * 100); //Increase range from 99% -> 99.00%
StateOfHealth = (LB_SOH * 100); //Increase range from 99% -> 99.00%
system_real_SOC_pptt = (LB_SOC * 10); //increase LB_SOC range from 0-100.0 -> 100.00
//Calculate the SOC% value to send to Fronius
soc_calculated = LB_SOC;
soc_calculated =
LB_MIN_SOC + (LB_MAX_SOC - LB_MIN_SOC) * (soc_calculated - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
if (soc_calculated < 0) { //We are in the real SOC% range of 0-20%, always set SOC sent to Inverter as 0%
soc_calculated = 0;
}
if (soc_calculated > 1000) { //We are in the real SOC% range of 80-100%, always set SOC sent to Inverter as 100%
soc_calculated = 1000;
}
SOC = (soc_calculated * 10); //increase LB_SOC range from 0-100.0 -> 100.00
system_battery_voltage_dV = LB_Battery_Voltage;
battery_voltage = LB_Battery_Voltage;
system_battery_current_dA = LB_Current;
battery_current = LB_Current;
capacity_Wh = BATTERY_WH_MAX; //Use the configured value to avoid overflows
system_capacity_Wh = BATTERY_WH_MAX; //Use the configured value to avoid overflows
//Calculate the remaining Wh amount from SOC% and max Wh value.
remaining_capacity_Wh = static_cast<int>((static_cast<double>(SOC) / 10000) * BATTERY_WH_MAX);
system_remaining_capacity_Wh = static_cast<int>((static_cast<double>(system_real_SOC_pptt) / 10000) * BATTERY_WH_MAX);
max_target_discharge_power;
system_max_discharge_power_W;
max_target_charge_power;
system_max_charge_power_W;
stat_batt_power;
system_active_power_W;
temperature_min;
system_temperature_min_dC;
temperature_max;
system_temperature_max_dC;
cell_min_voltage;
system_cell_min_voltage_mV;
cell_max_voltage;
system_cell_max_voltage_mV;
cell_deviation_mV = (cell_max_voltage - cell_min_voltage);
cell_deviation_mV = (system_cell_max_voltage_mV - system_cell_min_voltage_mV);
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
bms_status = FAULT;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
set_event(EVENT_CAN_FAILURE, 0);
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) {
bms_status = FAULT;
Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
}
if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) {
bms_status = FAULT;
Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
}
if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) {
LEDcolor = YELLOW;
Serial.println("ERROR: HIGH CELL mV DEVIATION!!! Inspect battery!");
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
#ifdef DEBUG_VIA_USB
Serial.println("Values going to inverter:");
Serial.print("SOH%: ");
Serial.print(StateOfHealth);
Serial.print(system_SOH_pptt);
Serial.print(", SOC% scaled: ");
Serial.print(SOC);
Serial.print(system_scaled_SOC_pptt);
Serial.print(", Voltage: ");
Serial.print(battery_voltage);
Serial.print(system_battery_voltage_dV);
Serial.print(", Max discharge power: ");
Serial.print(max_target_discharge_power);
Serial.print(system_max_discharge_power_W);
Serial.print(", Max charge power: ");
Serial.print(max_target_charge_power);
Serial.print(system_max_charge_power_W);
Serial.print(", Max temp: ");
Serial.print(temperature_max);
Serial.print(system_temperature_max_dC);
Serial.print(", Min temp: ");
Serial.print(temperature_min);
Serial.print(system_temperature_min_dC);
Serial.print(", BMS Status (3=OK): ");
Serial.print(bms_status);
Serial.print(system_bms_status);
Serial.println("Battery values: ");
Serial.print("Real SOC: ");
@ -143,7 +124,7 @@ void update_values_zoe_battery() { //This function maps all the values fetched
#endif
}
void receive_can_zoe_battery(CAN_frame_t rx_frame) {
void receive_can_battery(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0x42E: //HV SOC & Battery Temp & Charging Power
@ -157,7 +138,7 @@ void receive_can_zoe_battery(CAN_frame_t rx_frame) {
}
}
void send_can_zoe_battery() {
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100) {
@ -170,3 +151,12 @@ void send_can_zoe_battery() {
//ESP32Can.CANWriteFrame(&ZOE_423);
}
}
void setup_battery(void) { // Performs one time setup at startup
Serial.println("Renault Zoe battery selected");
system_max_design_voltage_dV = 4040; // 404.0V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
}
#endif

View file

@ -5,38 +5,37 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define ABSOLUTE_MAX_VOLTAGE \
4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
#define BATTERY_SELECTED
#define ABSOLUTE_CELL_MAX_VOLTAGE \
4100 // Max Cell Voltage mV! if voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_CELL_MIN_VOLTAGE \
3000 // Min Cell Voltage mV! if voltage goes under this, discharging further is disabled
#define MAX_CELL_DEVIATION_MV 500 //LED turns yellow on the board if mv delta exceeds this value
// These parameters need to be mapped for the Gen24
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t CANerror;
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
// These parameters need to be mapped for the inverter
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
void update_values_zoe_battery();
void receive_can_zoe_battery(CAN_frame_t rx_frame);
void send_can_zoe_battery();
void setup_battery(void);
#endif

View file

@ -1,7 +1,9 @@
#include "SANTA-FE-PHEV-BATTERY.h"
#include "BATTERIES.h"
#ifdef SANTA_FE_PHEV_BATTERY
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "SANTA-FE-PHEV-BATTERY.h"
/* Credits go to maciek16c for these findings!
https://github.com/maciek16c/hyundai-santa-fe-phev-battery
@ -19,9 +21,6 @@ static const int interval10 = 10; // interval (ms) at which send CAN
static const int interval100 = 100; // interval (ms) at which send CAN Messages
static uint8_t CANstillAlive = 12; //counter for checking if CAN is still alive
#define LB_MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to Inverter
#define LB_MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to Inverter
static int SOC_1 = 0;
static int SOC_2 = 0;
static int SOC_3 = 0;
@ -57,37 +56,34 @@ CAN_frame_t SANTAFE_523 = {.FIR = {.B =
.MsgID = 0x523,
.data = {0x60, 0x00, 0x60, 0, 0, 0, 0, 0}};
void update_values_santafe_phev_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
SOC;
system_real_SOC_pptt;
battery_voltage;
system_battery_voltage_dV;
battery_current;
system_battery_current_dA;
capacity_Wh = BATTERY_WH_MAX;
system_capacity_Wh = BATTERY_WH_MAX;
remaining_capacity_Wh;
system_remaining_capacity_Wh;
max_target_discharge_power;
system_max_discharge_power_W;
max_target_charge_power;
system_max_charge_power_W;
stat_batt_power;
system_active_power_W;
temperature_min;
system_temperature_min_dC;
temperature_max;
bms_status = ACTIVE; //Startout in active mode, then check safeties
system_temperature_max_dC;
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!CANstillAlive) {
bms_status = FAULT;
Serial.println("No CAN communication detected for 60s. Shutting down battery control.");
set_event(EVENT_CAN_FAILURE, 0);
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
CANstillAlive--;
clear_event(EVENT_CAN_RX_FAILURE);
}
#ifdef DEBUG_VIA_USB
@ -95,7 +91,7 @@ void update_values_santafe_phev_battery() { //This function maps all the values
#endif
}
void receive_can_santafe_phev_battery(CAN_frame_t rx_frame) {
void receive_can_battery(CAN_frame_t rx_frame) {
CANstillAlive = 12;
switch (rx_frame.MsgID) {
case 0x200:
@ -130,7 +126,7 @@ void receive_can_santafe_phev_battery(CAN_frame_t rx_frame) {
break;
}
}
void send_can_santafe_phev_battery() {
void send_can_battery() {
unsigned long currentMillis = millis();
//Send 10ms message
if (currentMillis - previousMillis10 >= interval10) {
@ -177,3 +173,12 @@ uint8_t CalculateCRC8(CAN_frame_t rx_frame) {
}
return (uint8_t)crc;
}
void setup_battery(void) { // Performs one time setup at startup
Serial.println("Hyundai Santa Fe PHEV battery selected");
system_max_design_voltage_dV = 4040; // 404.0V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
}
#endif

View file

@ -5,31 +5,31 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define ABSOLUTE_MAX_VOLTAGE \
4030 // 403.0V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
#define BATTERY_SELECTED
// These parameters need to be mapped for the Gen24
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint8_t bms_status;
extern uint8_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
// These parameters need to be mapped for the inverter
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
void update_values_santafe_phev_battery();
void receive_can_santafe_phev_battery(CAN_frame_t rx_frame);
void send_can_santafe_phev_battery();
uint8_t CalculateCRC8(CAN_frame_t rx_frame);
void setup_battery(void);
#endif

View file

@ -1,5 +1,7 @@
// SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp
#include "BATTERIES.h"
#ifdef SERIAL_LINK_RECEIVER
#include <Arduino.h>
#include "../devboard/utils/events.h"
#include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h"
#define INVERTER_SEND_NUM_VARIABLES 1
@ -26,28 +28,28 @@ SerialDataLink dataLinkReceive(SerialReceiver, 0, 0x01, sendingNumVariables,
static bool batteryFault = false; // used locally - mainly to indicate Battery CAN failure
void __getData() {
SOC = (uint16_t)dataLinkReceive.getReceivedData(0);
StateOfHealth = (uint16_t)dataLinkReceive.getReceivedData(1);
battery_voltage = (uint16_t)dataLinkReceive.getReceivedData(2);
battery_current = (uint16_t)dataLinkReceive.getReceivedData(3);
capacity_Wh = (uint16_t)dataLinkReceive.getReceivedData(4);
remaining_capacity_Wh = (uint16_t)dataLinkReceive.getReceivedData(5);
max_target_discharge_power = (uint16_t)dataLinkReceive.getReceivedData(6);
max_target_charge_power = (uint16_t)dataLinkReceive.getReceivedData(7);
uint16_t _bms_status = (uint16_t)dataLinkReceive.getReceivedData(8);
bms_status = _bms_status;
bms_char_dis_status = (uint16_t)dataLinkReceive.getReceivedData(9);
stat_batt_power = (uint16_t)dataLinkReceive.getReceivedData(10);
temperature_min = (uint16_t)dataLinkReceive.getReceivedData(11);
temperature_max = (uint16_t)dataLinkReceive.getReceivedData(12);
cell_max_voltage = (uint16_t)dataLinkReceive.getReceivedData(13);
cell_min_voltage = (uint16_t)dataLinkReceive.getReceivedData(14);
LFP_Chemistry = (bool)dataLinkReceive.getReceivedData(15);
batteryAllowsContactorClosing = (uint16_t)dataLinkReceive.getReceivedData(16);
system_real_SOC_pptt = (uint16_t)dataLinkReceive.getReceivedData(0);
system_SOH_pptt = (uint16_t)dataLinkReceive.getReceivedData(1);
system_battery_voltage_dV = (uint16_t)dataLinkReceive.getReceivedData(2);
system_battery_current_dA = (int16_t)dataLinkReceive.getReceivedData(3);
system_capacity_Wh = (uint32_t)dataLinkReceive.getReceivedData(4);
system_remaining_capacity_Wh = (uint32_t)dataLinkReceive.getReceivedData(5);
system_max_discharge_power_W = (uint16_t)dataLinkReceive.getReceivedData(6);
system_max_charge_power_W = (uint16_t)dataLinkReceive.getReceivedData(7);
uint16_t _system_bms_status = (uint16_t)dataLinkReceive.getReceivedData(8);
system_active_power_W = (uint16_t)dataLinkReceive.getReceivedData(9);
system_temperature_min_dC = (int16_t)dataLinkReceive.getReceivedData(10);
system_temperature_max_dC = (int16_t)dataLinkReceive.getReceivedData(11);
system_cell_max_voltage_mV = (uint16_t)dataLinkReceive.getReceivedData(12);
system_cell_min_voltage_mV = (uint16_t)dataLinkReceive.getReceivedData(13);
system_LFP_Chemistry = (bool)dataLinkReceive.getReceivedData(14);
batteryAllowsContactorClosing = (bool)dataLinkReceive.getReceivedData(15);
batteryFault = false;
if (_bms_status == FAULT)
if (_system_bms_status == FAULT) {
batteryFault = true;
set_event(EVENT_SERIAL_TRANSMITTER_FAILURE, 0);
}
}
void updateData() {
@ -97,8 +99,8 @@ void manageSerialLinkReceiver() {
{
__getData();
reads++;
lastGoodMaxCharge = max_target_charge_power;
lastGoodMaxDischarge = max_target_discharge_power;
lastGoodMaxCharge = system_max_charge_power_W;
lastGoodMaxDischarge = system_max_discharge_power_W;
//--- if BatteryFault then assume Data is stale
if (!batteryFault)
lastGood = currentTime;
@ -114,14 +116,14 @@ void manageSerialLinkReceiver() {
if (minutesLost > 0 && lastGood > 0) {
// lose 25% each minute of data loss
if (minutesLost < 4) {
max_target_charge_power = (lastGoodMaxCharge * (4 - minutesLost)) / 4;
max_target_discharge_power = (lastGoodMaxDischarge * (4 - minutesLost)) / 4;
system_max_charge_power_W = (lastGoodMaxCharge * (4 - minutesLost)) / 4;
system_max_discharge_power_W = (lastGoodMaxDischarge * (4 - minutesLost)) / 4;
set_event(EVENT_SERIAL_RX_WARNING, minutesLost);
} else {
// Times Up -
max_target_charge_power = 0;
max_target_discharge_power = 0;
bms_status = 4; //Fault state
LEDcolor = RED;
system_max_charge_power_W = 0;
system_max_discharge_power_W = 0;
set_event(EVENT_SERIAL_RX_FAILURE, uint8_t(min(minutesLost, 255uL)));
//----- Throw Error
}
// report Lost data & Max charge / Discharge reductions
@ -135,9 +137,9 @@ void manageSerialLinkReceiver() {
}
Serial.print(minutesLost);
Serial.print(", max Charge = ");
Serial.print(max_target_charge_power);
Serial.print(system_max_charge_power_W);
Serial.print(", max Discharge = ");
Serial.println(max_target_discharge_power);
Serial.println(system_max_discharge_power_W);
}
}
@ -174,37 +176,35 @@ void manageSerialLinkReceiver() {
void update_values_serial_link() {
Serial.println("Values from battery: ");
Serial.print("SOC: ");
Serial.print(SOC);
Serial.print(system_real_SOC_pptt);
Serial.print(" SOH: ");
Serial.print(StateOfHealth);
Serial.print(system_SOH_pptt);
Serial.print(" Voltage: ");
Serial.print(battery_voltage);
Serial.print(system_battery_voltage_dV);
Serial.print(" Current: ");
Serial.print(battery_current);
Serial.print(system_battery_current_dA);
Serial.print(" Capacity: ");
Serial.print(capacity_Wh);
Serial.print(system_capacity_Wh);
Serial.print(" Remain cap: ");
Serial.print(remaining_capacity_Wh);
Serial.print(system_remaining_capacity_Wh);
Serial.print(" Max discharge W: ");
Serial.print(max_target_discharge_power);
Serial.print(system_max_discharge_power_W);
Serial.print(" Max charge W: ");
Serial.print(max_target_charge_power);
Serial.print(system_max_charge_power_W);
Serial.print(" BMS status: ");
Serial.print(bms_status);
Serial.print(" BMS status dis/cha: ");
Serial.print(bms_char_dis_status);
Serial.print(system_bms_status);
Serial.print(" Power: ");
Serial.print(stat_batt_power);
Serial.print(system_active_power_W);
Serial.print(" Temp min: ");
Serial.print(temperature_min);
Serial.print(system_temperature_min_dC);
Serial.print(" Temp max: ");
Serial.print(temperature_max);
Serial.print(system_temperature_max_dC);
Serial.print(" Cell max: ");
Serial.print(cell_max_voltage);
Serial.print(system_cell_max_voltage_mV);
Serial.print(" Cell min: ");
Serial.print(cell_min_voltage);
Serial.print(system_cell_min_voltage_mV);
Serial.print(" LFP : ");
Serial.print(LFP_Chemistry);
Serial.print(system_LFP_Chemistry);
Serial.print(" batteryAllowsContactorClosing: ");
Serial.print(batteryAllowsContactorClosing);
Serial.print(" inverterAllowsContactorClosing: ");
@ -212,3 +212,11 @@ void update_values_serial_link() {
Serial.println("");
}
void setup_battery(void) {
Serial.println("SERIAL_DATA_LINK_RECEIVER selected");
}
void update_values_battery() {}
void send_can_battery() {}
#endif

View file

@ -3,6 +3,8 @@
#ifndef SERIAL_LINK_RECEIVER_FROM_BATTERY_H
#define SERIAL_LINK_RECEIVER_FROM_BATTERY_H
#define BATTERY_SELECTED
#include <Arduino.h>
#include "../../USER_SETTINGS.h"
#include "../devboard/config.h" // Needed for all defines
@ -10,37 +12,31 @@
// https://github.com/mackelec/SerialDataLink
#define ABSOLUTE_MAX_VOLTAGE \
4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
// These parameters need to be mapped for the inverter
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint8_t LEDcolor; //Enum, 0-10
extern bool LFP_Chemistry;
extern uint16_t CANerror;
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
// These parameters need to be mapped on the battery side
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint8_t system_bms_status; //Enum 0-5
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern bool system_LFP_Chemistry; //Set to true or false depending on cell chemistry
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
// Parameters to send to the transmitter
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
void manageSerialLinkReceiver();
void update_values_serial_link();
void setup_battery(void);
#endif

View file

@ -1,7 +1,9 @@
#include "TESLA-MODEL-3-BATTERY.h"
#include "BATTERIES.h"
#ifdef TESLA_MODEL_3_BATTERY
#include "../devboard/utils/events.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "TESLA-MODEL-3-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
/* Credits: Most of the code comes from Per Carlen's bms_comms_tesla_model3.py (https://gitlab.com/pelle8/batt2gen24/) */
@ -48,17 +50,16 @@ static uint16_t regenerative_limit = 0;
static uint16_t discharge_limit = 0;
static uint16_t max_heat_park = 0;
static uint16_t hvac_max_power = 0;
static uint16_t min_voltage = 0;
static uint16_t max_discharge_current = 0;
static uint16_t max_charge_current = 0;
static uint16_t max_voltage = 0;
static uint16_t bms_max_voltage = 0;
static uint16_t bms_min_voltage = 0;
static uint16_t high_voltage = 0;
static uint16_t low_voltage = 0;
static uint16_t output_current = 0;
static uint16_t soc_min = 0;
static uint16_t soc_max = 0;
static uint16_t soc_vi = 0;
static uint16_t soc_calculated = 0;
static uint16_t soc_ave = 0;
static uint16_t cell_max_v = 3700;
static uint16_t cell_min_v = 3700;
@ -151,117 +152,119 @@ static const char* hvilStatusState[] = {"NOT OK",
"UNKNOWN(14)",
"UNKNOWN(15)"};
#define MAX_SOC 1000 //BMS never goes over this value. We use this info to rescale SOC% sent to inverter
#define MIN_SOC 0 //BMS never goes below this value. We use this info to rescale SOC% sent to inverter
#define MAX_CELL_VOLTAGE_NCA_NCM 4250 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_NCA_NCM 2950 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_NCA_NCM 500 //LED turns yellow on the board if mv delta exceeds this value
#define MAX_CELL_VOLTAGE_LFP 3500 //Battery is put into emergency stop if one cell goes over this value
#define MAX_CELL_VOLTAGE_LFP 3520 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_LFP 2800 //Battery is put into emergency stop if one cell goes below this value
#define MAX_CELL_DEVIATION_LFP 150 //LED turns yellow on the board if mv delta exceeds this value
#define REASONABLE_ENERGYAMOUNT 20 //When the BMS stops making sense on some values, they are always <20
void update_values_tesla_model_3_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
//After values are mapped, we perform some safety checks, and do some serial printouts
//Calculate the SOH% to send to inverter
if (bat_beginning_of_life != 0) { //div/0 safeguard
StateOfHealth =
system_SOH_pptt =
static_cast<uint16_t>((static_cast<double>(nominal_full_pack_energy) / bat_beginning_of_life) * 10000.0);
}
//If the calculation went wrong, set SOH to 100%
if (system_SOH_pptt > 10000) {
system_SOH_pptt = 10000;
}
//If the value is unavailable, set SOH to 99%
if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) {
StateOfHealth = 9900;
system_SOH_pptt = 9900;
}
//Calculate the SOC% value to send to inverter
soc_calculated = soc_vi;
soc_calculated = MIN_SOC + (MAX_SOC - MIN_SOC) * (soc_calculated - MINPERCENTAGE) / (MAXPERCENTAGE - MINPERCENTAGE);
if (soc_calculated < 0) { //We are in the real SOC% range of 0-20%, always set SOC sent to Inverter as 0%
soc_calculated = 0;
}
if (soc_calculated > 1000) { //We are in the real SOC% range of 80-100%, always set SOC sent to Inverter as 100%
soc_calculated = 1000;
}
SOC = (soc_calculated * 10); //increase SOC range from 0-100.0 -> 100.00
system_real_SOC_pptt = (soc_vi * 10); //increase SOC range from 0-100.0 -> 100.00
battery_voltage = (volts * 10); //One more decimal needed (370 -> 3700)
system_battery_voltage_dV = (volts * 10); //One more decimal needed (370 -> 3700)
battery_current = convert2unsignedInt16(amps); //13.0A
system_battery_current_dA = amps; //13.0A
capacity_Wh = BATTERY_WH_MAX; //Use the configured value to avoid overflows
system_capacity_Wh = BATTERY_WH_MAX; //Use the configured value to avoid overflows
//Calculate the remaining Wh amount from SOC% and max Wh value.
remaining_capacity_Wh = static_cast<uint16_t>((static_cast<double>(SOC) / 10000) * BATTERY_WH_MAX);
system_remaining_capacity_Wh =
static_cast<uint32_t>((static_cast<double>(system_real_SOC_pptt) / 10000) * BATTERY_WH_MAX);
// Define the allowed discharge power
max_target_discharge_power = (max_discharge_current * volts);
system_max_discharge_power_W = (max_discharge_current * volts);
// Cap the allowed discharge power if battery is empty, or discharge power is higher than the maximum discharge power allowed
if (SOC == 0) {
max_target_discharge_power = 0;
} else if (max_target_discharge_power > MAXDISCHARGEPOWERALLOWED) {
max_target_discharge_power = MAXDISCHARGEPOWERALLOWED;
if (system_scaled_SOC_pptt == 0) {
system_max_discharge_power_W = 0;
} else if (system_max_discharge_power_W > MAXDISCHARGEPOWERALLOWED) {
system_max_discharge_power_W = MAXDISCHARGEPOWERALLOWED;
}
//The allowed charge power behaves strangely. We instead estimate this value
if (SOC == 10000) { // When scaled SOC is 100%, set allowed charge power to 0
max_target_charge_power = 0;
if (system_scaled_SOC_pptt == 10000) { // When scaled SOC is 100%, set allowed charge power to 0
system_max_charge_power_W = 0;
} else if (soc_vi > 950) { // When real SOC is between 95-99.99%, ramp the value between Max<->0
max_target_charge_power = MAXCHARGEPOWERALLOWED * (1 - (soc_vi - 950) / 50.0);
system_max_charge_power_W = MAXCHARGEPOWERALLOWED * (1 - (soc_vi - 950) / 50.0);
} else { // No limits, max charging power allowed
max_target_charge_power = MAXCHARGEPOWERALLOWED;
system_max_charge_power_W = MAXCHARGEPOWERALLOWED;
}
power = ((volts / 10) * amps);
stat_batt_power = convert2unsignedInt16(power);
system_active_power_W = power;
temperature_min = convert2unsignedInt16(min_temp);
system_temperature_min_dC = min_temp;
temperature_max = convert2unsignedInt16(max_temp);
system_temperature_max_dC = max_temp;
cell_max_voltage = cell_max_v;
system_cell_max_voltage_mV = cell_max_v;
cell_min_voltage = cell_min_v;
system_cell_min_voltage_mV = cell_min_v;
/* Value mapping is completed. Start to check all safeties */
bms_status = ACTIVE; //Startout in active mode before checking if we have any faults
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
if (!stillAliveCAN) {
bms_status = FAULT;
Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control.");
set_event(EVENT_CAN_FAILURE, 0);
set_event(EVENT_CAN_RX_FAILURE, 0);
} else {
stillAliveCAN--;
clear_event(EVENT_CAN_RX_FAILURE);
}
if (hvil_status == 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use
bms_status = FAULT;
Serial.println("ERROR: High voltage cable removed while battery running. Opening contactors!");
set_event(EVENT_INTERNAL_OPEN_FAULT, 0);
} else {
clear_event(EVENT_INTERNAL_OPEN_FAULT);
}
cell_deviation_mV = (cell_max_v - cell_min_v);
//Determine which chemistry battery pack is using (crude method, TODO: replace with real CAN data later)
//Determine which chemistry battery pack is using (crude method, TODO: replace with real CAN identifier later)
if (soc_vi > 900) { //When SOC% is over 90.0%, we can use max cell voltage to estimate what chemistry is used
if (cell_max_v < 3450) {
LFP_Chemistry = true;
system_LFP_Chemistry = true;
}
if (cell_max_v > 3700) {
LFP_Chemistry = false;
system_LFP_Chemistry = false;
}
}
// An even better way is to check how many cells are in the pack. NCM/A batteries have 96s, LFP has 102-106s
if (system_number_of_cells > 101) {
system_LFP_Chemistry = true;
}
//Once cell chemistry is determined, set maximum and minimum total pack voltage safety limits
if (system_LFP_Chemistry) {
system_max_design_voltage_dV = 3880;
system_min_design_voltage_dV = 2968;
} else { // NCM/A chemistry
system_max_design_voltage_dV = 4030;
system_min_design_voltage_dV = 3100;
}
//Check if SOC% is plausible
if (battery_voltage >
(ABSOLUTE_MAX_VOLTAGE - 100)) { // When pack voltage is close to max, and SOC% is still low, raise FAULT
if (SOC < 6500) { //When SOC is less than 65.00% when approaching max voltage
bms_status = FAULT;
Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!");
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, SOC / 100);
if (system_battery_voltage_dV >
(system_max_design_voltage_dV - 20)) { // When pack voltage is close to max, and SOC% is still low, raise FAULT
if (system_real_SOC_pptt < 5000) { //When SOC is less than 50.00% when approaching max voltage
set_event(EVENT_SOC_PLAUSIBILITY_ERROR, system_real_SOC_pptt / 100);
}
}
@ -270,49 +273,40 @@ void update_values_tesla_model_3_battery() { //This function maps all the value
Serial.println("Warning: kWh remaining " + String(nominal_full_pack_energy) +
" reported by battery not plausible. Battery needs cycling.");
set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy);
LEDcolor = YELLOW;
} else if (nominal_full_pack_energy <= 1) {
Serial.println("Info: kWh remaining battery is not reporting kWh remaining.");
set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy);
}
if (LFP_Chemistry) { //LFP limits used for voltage safeties
if (system_LFP_Chemistry) { //LFP limits used for voltage safeties
if (cell_max_v >= MAX_CELL_VOLTAGE_LFP) {
bms_status = FAULT;
Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
set_event(EVENT_CELL_OVER_VOLTAGE, (cell_max_v - MAX_CELL_VOLTAGE_LFP));
}
if (cell_min_v <= MIN_CELL_VOLTAGE_LFP) {
bms_status = FAULT;
Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
set_event(EVENT_CELL_UNDER_VOLTAGE, (MIN_CELL_VOLTAGE_LFP - cell_min_v));
}
if (cell_deviation_mV > MAX_CELL_DEVIATION_LFP) {
LEDcolor = YELLOW;
Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!");
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
set_event(EVENT_CELL_DEVIATION_HIGH, cell_deviation_mV);
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
} else { //NCA/NCM limits used
if (cell_max_v >= MAX_CELL_VOLTAGE_NCA_NCM) {
bms_status = FAULT;
Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
set_event(EVENT_CELL_OVER_VOLTAGE, 0);
set_event(EVENT_CELL_OVER_VOLTAGE, (cell_max_v - MAX_CELL_VOLTAGE_NCA_NCM));
}
if (cell_min_v <= MIN_CELL_VOLTAGE_NCA_NCM) {
bms_status = FAULT;
Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!");
set_event(EVENT_CELL_UNDER_VOLTAGE, 0);
set_event(EVENT_CELL_UNDER_VOLTAGE, (MIN_CELL_VOLTAGE_NCA_NCM - cell_min_v));
}
if (cell_deviation_mV > MAX_CELL_DEVIATION_NCA_NCM) {
LEDcolor = YELLOW;
Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!");
set_event(EVENT_CELL_DEVIATION_HIGH, 0);
set_event(EVENT_CELL_DEVIATION_HIGH, cell_deviation_mV);
} else {
clear_event(EVENT_CELL_DEVIATION_HIGH);
}
}
if (bms_status == FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
max_target_charge_power = 0;
max_target_discharge_power = 0;
if (system_bms_status == FAULT) { //Incase we enter a critical fault state, zero out the allowed limits
system_max_charge_power_W = 0;
system_max_discharge_power_W = 0;
}
/* Safeties verified. Perform USB serial printout if configured to do so */
@ -346,7 +340,7 @@ void update_values_tesla_model_3_battery() { //This function maps all the value
Serial.print("YES, ");
else
Serial.print("NO, ");
if (LFP_Chemistry) {
if (system_LFP_Chemistry) {
Serial.print("LFP chemistry detected!");
}
Serial.println("");
@ -370,19 +364,19 @@ void update_values_tesla_model_3_battery() { //This function maps all the value
Serial.println("");
Serial.println("Values passed to the inverter: ");
print_SOC(" SOC: ", SOC);
print_int_with_units(" Max discharge power: ", max_target_discharge_power, "W");
print_SOC(" SOC: ", system_scaled_SOC_pptt);
print_int_with_units(" Max discharge power: ", system_max_discharge_power_W, "W");
Serial.print(", ");
print_int_with_units(" Max charge power: ", max_target_charge_power, "W");
print_int_with_units(" Max charge power: ", system_max_charge_power_W, "W");
Serial.println("");
print_int_with_units(" Max temperature: ", ((int16_t)temperature_max * 0.1), "°C");
print_int_with_units(" Max temperature: ", ((int16_t)system_temperature_min_dC * 0.1), "°C");
Serial.print(", ");
print_int_with_units(" Min temperature: ", ((int16_t)temperature_min * 0.1), "°C");
print_int_with_units(" Min temperature: ", ((int16_t)system_temperature_max_dC * 0.1), "°C");
Serial.println("");
#endif
}
void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame) {
void receive_can_battery(CAN_frame_t rx_frame) {
static int mux = 0;
static int temp = 0;
@ -473,11 +467,11 @@ void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame) {
{
// Example, frame3=0x89,frame2=0x1D = 35101 / 10 = 3510mV
volts = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) / 10;
cellvoltages[mux * 3] = volts;
system_cellvoltages_mV[mux * 3] = volts;
volts = ((rx_frame.data.u8[5] << 8) | rx_frame.data.u8[4]) / 10;
cellvoltages[1 + mux * 3] = volts;
system_cellvoltages_mV[1 + mux * 3] = volts;
volts = ((rx_frame.data.u8[7] << 8) | rx_frame.data.u8[6]) / 10;
cellvoltages[2 + mux * 3] = volts;
system_cellvoltages_mV[2 + mux * 3] = volts;
// Track the max value of mux. If we've seen two 0 values for mux, we've probably gathered all
// cell voltages. Then, 2 + mux_max * 3 + 1 is the number of cell voltages.
@ -486,7 +480,7 @@ void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame) {
mux_zero_counter++;
if (mux_zero_counter == 2u) {
// The max index will be 2 + mux_max * 3 (see above), so "+ 1" for the number of cells
nof_cellvoltages = 2 + 3 * mux_max + 1;
system_number_of_cells = 2 + 3 * mux_max + 1;
// Increase the counter arbitrarily another time to make the initial if-statement evaluate to false
mux_zero_counter++;
}
@ -495,8 +489,10 @@ void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame) {
break;
case 0x2d2:
//Min / max limits
min_voltage = ((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V
max_voltage = ((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01 * 2; //Example 40282mv * 0.01 = 402.82 V
bms_min_voltage =
((rx_frame.data.u8[1] << 8) | rx_frame.data.u8[0]) * 0.01 * 2; //Example 24148mv * 0.01 = 241.48 V
bms_max_voltage =
((rx_frame.data.u8[3] << 8) | rx_frame.data.u8[2]) * 0.01 * 2; //Example 40282mv * 0.01 = 402.82 V
max_charge_current =
(((rx_frame.data.u8[5] & 0x3F) << 8) | rx_frame.data.u8[4]) * 0.1; //Example 1301? * 0.1 = 130.1?
max_discharge_current =
@ -572,7 +568,7 @@ void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame) {
break;
}
}
void send_can_tesla_model_3_battery() {
void send_can_battery() {
/*From bielec: My fist 221 message, to close the contactors is 0x41, 0x11, 0x01, 0x00, 0x00, 0x00, 0x20, 0x96 and then,
to cause "hv_up_for_drive" I send an additional 221 message 0x61, 0x15, 0x01, 0x00, 0x00, 0x00, 0x20, 0xBA so
two 221 messages are being continuously transmitted. When I want to shut down, I stop the second message and only send
@ -584,12 +580,12 @@ the first, for a few cycles, then stop all messages which causes the contactor
previousMillis30 = currentMillis;
if (inverterAllowsContactorClosing == 1) {
if (bms_status == ACTIVE) {
if (system_bms_status == ACTIVE) {
send221still = 50;
batteryAllowsContactorClosing = true;
ESP32Can.CANWriteFrame(&TESLA_221_1);
ESP32Can.CANWriteFrame(&TESLA_221_2);
} else { //bms_status == FAULT or inverter requested opening contactors
} else { //system_bms_status == FAULT or inverter requested opening contactors
if (send221still > 0) {
batteryAllowsContactorClosing = false;
ESP32Can.CANWriteFrame(&TESLA_221_1);
@ -599,13 +595,6 @@ the first, for a few cycles, then stop all messages which causes the contactor
}
}
}
uint16_t convert2unsignedInt16(int16_t signed_value) {
if (signed_value < 0) {
return (65535 + signed_value);
} else {
return (uint16_t)signed_value;
}
}
void print_int_with_units(char* header, int value, char* units) {
Serial.print(header);
@ -697,3 +686,12 @@ void printDebugIfActive(uint8_t symbol, const char* message) {
Serial.println(message);
}
}
void setup_battery(void) { // Performs one time setup at startup
Serial.println("Tesla Model 3 battery selected");
system_max_design_voltage_dV = 4030; // 403.0V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 3100; // 310.0V under this, discharging further is disabled
}
#endif

View file

@ -5,43 +5,40 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define ABSOLUTE_MAX_VOLTAGE \
4030 // 403.0V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 2450 // 245.0V if battery voltage goes under this, discharging further is disabled
#define BATTERY_SELECTED
#define MAXCHARGEPOWERALLOWED 15000 // 15000W we use a define since the value supplied by Tesla is always 0
#define MAXDISCHARGEPOWERALLOWED \
60000 // 60000W we need to cap this value to max 60kW, most inverters overflow otherwise
// These parameters need to be mapped for the Inverter
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern uint8_t nof_cellvoltages; // Total number of cell voltages, set by each battery.
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool LFP_Chemistry;
// These parameters need to be mapped for the inverter
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool system_LFP_Chemistry; //Bool, 1=true, 0=false
void update_values_tesla_model_3_battery();
void receive_can_tesla_model_3_battery(CAN_frame_t rx_frame);
void send_can_tesla_model_3_battery();
void printFaultCodesIfActive();
void printDebugIfActive(uint8_t symbol, const char* message);
void print_int_with_units(char* header, int value, char* units);
void print_SOC(char* header, int SOC);
uint16_t convert2unsignedInt16(int16_t signed_value);
void setup_battery(void);
#endif

View file

@ -1,6 +1,8 @@
#include "TEST-FAKE-BATTERY.h"
#include "BATTERIES.h"
#ifdef TEST_FAKE_BATTERY
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#include "TEST-FAKE-BATTERY.h"
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis10 = 0; // will store last time a 10ms CAN Message was send
@ -16,58 +18,54 @@ void print_units(char* header, int value, char* units) {
Serial.print(units);
}
void update_values_test_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
bms_status = ACTIVE; //Always be in Active mode
void update_values_battery() { /* This function puts fake values onto the parameters sent towards the inverter */
system_real_SOC_pptt = 5000; // 50.00%
LEDcolor = TEST_ALL_COLORS; // Cycle the LED thru all available colors
system_SOH_pptt = 9900; // 99.00%
SOC = 5000; // 50.00%
//system_battery_voltage_dV = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
StateOfHealth = 9900; // 99.00%
system_battery_current_dA = 0; // 0 A
//battery_voltage = 3700; // 370.0V , value set in startup in .ino file, editable via webUI
system_capacity_Wh = 30000; // 30kWh
battery_current = 0; // 0 A
system_remaining_capacity_Wh = 15000; // 15kWh
capacity_Wh = 30000; // 30kWh
system_cell_max_voltage_mV = 3596;
remaining_capacity_Wh = 15000; // 15kWh
system_cell_min_voltage_mV = 3500;
cell_max_voltage = 3596;
system_active_power_W = 0; // 0W
cell_min_voltage = 3500;
system_temperature_min_dC = 50; // 5.0*C
stat_batt_power = 0; // 0W
system_temperature_max_dC = 60; // 6.0*C
temperature_min = 50; // 5.0*C
system_max_discharge_power_W = 5000; // 5kW
temperature_max = 60; // 6.0*C
max_target_discharge_power = 5000; // 5kW
max_target_charge_power = 5000; // 5kW
system_max_charge_power_W = 5000; // 5kW
for (int i = 0; i < 97; ++i) {
cellvoltages[i] = 3500 + i;
system_cellvoltages_mV[i] = 3500 + i;
}
/*Finally print out values to serial if configured to do so*/
#ifdef DEBUG_VIA_USB
Serial.println("FAKE Values going to inverter");
print_units("SOH%: ", (StateOfHealth * 0.01), "% ");
print_units(", SOC%: ", (SOC * 0.01), "% ");
print_units(", Voltage: ", (battery_voltage * 0.1), "V ");
print_units(", Max discharge power: ", max_target_discharge_power, "W ");
print_units(", Max charge power: ", max_target_charge_power, "W ");
print_units(", Max temp: ", (temperature_max * 0.1), "°C ");
print_units(", Min temp: ", (temperature_min * 0.1), "°C ");
print_units(", Max cell voltage: ", cell_max_voltage, "mV ");
print_units(", Min cell voltage: ", cell_min_voltage, "mV ");
print_units("SOH%: ", (system_SOH_pptt * 0.01), "% ");
print_units(", SOC%: ", (system_scaled_SOC_pptt * 0.01), "% ");
print_units(", Voltage: ", (system_battery_voltage_dV * 0.1), "V ");
print_units(", Max discharge power: ", system_max_discharge_power_W, "W ");
print_units(", Max charge power: ", system_max_charge_power_W, "W ");
print_units(", Max temp: ", (system_temperature_max_dC * 0.1), "°C ");
print_units(", Min temp: ", (system_temperature_min_dC * 0.1), "°C ");
print_units(", Max cell voltage: ", system_cell_max_voltage_mV, "mV ");
print_units(", Min cell voltage: ", system_cell_min_voltage_mV, "mV ");
Serial.println("");
#endif
}
void receive_can_test_battery(CAN_frame_t rx_frame) {
void receive_can_battery(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0xABC:
break;
@ -75,7 +73,7 @@ void receive_can_test_battery(CAN_frame_t rx_frame) {
break;
}
}
void send_can_test_battery() {
void send_can_battery() {
unsigned long currentMillis = millis();
// Send 100ms CAN Message
if (currentMillis - previousMillis100 >= interval100) {
@ -83,3 +81,12 @@ void send_can_test_battery() {
// Put fake messages here incase you want to test sending CAN
}
}
void setup_battery(void) { // Performs one time setup at startup
Serial.println("Test mode with fake battery selected");
system_max_design_voltage_dV = 4040; // 404.4V, over this, charging is not possible (goes into forced discharge)
system_min_design_voltage_dV = 2450; // 245.0V under this, discharging further is disabled
}
#endif

View file

@ -5,33 +5,31 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
#define ABSOLUTE_MAX_VOLTAGE \
4040 // 404.4V,if battery voltage goes over this, charging is not possible (goes into forced discharge)
#define ABSOLUTE_MIN_VOLTAGE 3100 // 310.0V if battery voltage goes under this, discharging further is disabled
#define BATTERY_SELECTED
// These parameters need to be mapped for the inverter
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t cellvoltages[120]; //mV 0-5000 per cell
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint8_t LEDcolor; //Enum, 0-10
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, true/false
void update_values_test_battery();
void receive_can_test_battery(CAN_frame_t rx_frame);
void send_can_test_battery();
void setup_battery(void);
#endif

View file

@ -227,12 +227,12 @@ void send_can_nissanleaf_charger() {
}
// if actual battery_voltage is less than setpoint got to max power set from web ui
if (battery_voltage < (CHARGER_SET_HV * 10)) { //battery_voltage = V+1, 0-500.0 (0-5000)
if (system_battery_voltage_dV < (CHARGER_SET_HV * 10)) { //system_battery_voltage_dV = V+1, 0-500.0 (0-5000)
OBCpower = OBCpowerSetpoint;
}
// decrement charger power if volt setpoint is reached
if (battery_voltage >= (CHARGER_SET_HV * 10)) {
if (system_battery_voltage_dV >= (CHARGER_SET_HV * 10)) {
if (OBCpower > 0x64) {
OBCpower--;
}

View file

@ -4,7 +4,7 @@
#include "../../USER_SETTINGS.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
void send_can_nissanleaf_charger();
void receive_can_nissanleaf_charger(CAN_frame_t rx_frame);

View file

@ -36,11 +36,11 @@
#define SD_CS_PIN 13
#define WS2812_PIN 4
// LED definitions for the board
// LED definitions for the board, in order of "priority", DONT CHANGE!
#define GREEN 0
#define YELLOW 1
#define RED 2
#define BLUE 3
#define BLUE 2
#define RED 3
#define TEST_ALL_COLORS 10
// Inverter definitions

View file

@ -4,6 +4,7 @@
#include <freertos/FreeRTOS.h>
#include "../../../USER_SETTINGS.h"
#include "../../battery/BATTERIES.h"
#include "../../lib/bblanchon-ArduinoJson/ArduinoJson.h"
#include "../../lib/knolleary-pubsubclient/PubSubClient.h"
#include "../utils/timer.h"
@ -26,84 +27,73 @@ static void publish_values(void) {
publish_cell_voltages();
}
static String generateCellVoltageAutoConfigTopic(int cell_number, const char* hostname) {
return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/cell_voltage" + String(cell_number) +
"/config";
}
static void publish_cell_voltages(void) {
static bool mqtt_first_transmission = true;
static JsonDocument doc;
static const char* hostname = WiFi.getHostname();
static String state_topic = String("battery-emulator_") + String(hostname) + "/spec_data";
// If the cell voltage number isn't initialized...
if (nof_cellvoltages == 0u) {
if (system_number_of_cells == 0u) {
return;
}
// At startup, re-post the discovery message for home assistant
if (mqtt_first_transmission == true) {
mqtt_first_transmission = false;
// Base topic for any cell voltage "sensor"
String topic = "homeassistant/sensor/battery-emulator/cell_voltage";
for (int i = 0; i < nof_cellvoltages; i++) {
// Build JSON message with device configuration for each cell voltage
// Probably shouldn't be BatteryEmulator here, instead "LeafBattery"
// or similar but hey, it works.
// mqtt_msg is a global buffer, should be fine since we run too much
// in a single thread :)
snprintf(mqtt_msg, sizeof(mqtt_msg),
"{"
"\"device\": {"
"\"identifiers\": ["
"\"battery-emulator\""
"],"
"\"manufacturer\": \"DalaTech\","
"\"model\": \"BatteryEmulator\","
"\"name\": \"BatteryEmulator\""
"},"
"\"device_class\": \"voltage\","
"\"enabled_by_default\": true,"
"\"object_id\": \"sensor_battery_voltage_cell%d\","
"\"origin\": {"
"\"name\": \"BatteryEmulator\","
"\"sw\": \"%s-mqtt\","
"\"url\": \"https://github.com/dalathegreat/Battery-Emulator\""
"},"
"\"state_class\": \"measurement\","
"\"name\": \"Battery Cell Voltage %d\","
"\"state_topic\": \"battery-emulator/spec_data\","
"\"unique_id\": \"battery-emulator_battery_voltage_cell%d\","
"\"unit_of_measurement\": \"V\","
"\"value_template\": \"{{ value_json.cell_voltages[%d] }}\""
"}",
i + 1, version_number, i + 1, i + 1, i);
// End each discovery topic with cell number and '/config'
String cell_topic = topic + String(i + 1) + "/config";
mqtt_publish_retain(cell_topic.c_str());
}
} else {
// Every 5-ish seconds, build the JSON payload for the state topic. This requires
// some annoying formatting due to C++ not having nice Python-like string formatting.
// msg_length is a cumulative variable to track start position (param 1) and for
// modifying the maxiumum amount of characters to write (param 2). The third parameter
// is the string content
for (int i = 0; i < system_number_of_cells; i++) {
int cellNumber = i + 1;
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));
mqtt_publish(generateCellVoltageAutoConfigTopic(cellNumber, hostname).c_str(), mqtt_msg, true);
}
doc.clear(); // clear after sending autoconfig
} else {
// If cell voltages haven't been populated...
if (nof_cellvoltages == 0u) {
if (system_number_of_cells == 0u) {
return;
}
}
size_t msg_length = snprintf(mqtt_msg, sizeof(mqtt_msg), "{\n\"cell_voltages\":[");
for (size_t i = 0; i < nof_cellvoltages; ++i) {
msg_length += snprintf(mqtt_msg + msg_length, sizeof(mqtt_msg) - msg_length, "%s%.3f", (i == 0) ? "" : ", ",
((float)cellvoltages[i]) / 1000);
}
snprintf(mqtt_msg + msg_length, sizeof(mqtt_msg) - msg_length, "]\n}\n");
JsonArray cell_voltages = doc["cell_voltages"].to<JsonArray>();
for (size_t i = 0; i < system_number_of_cells; ++i) {
cell_voltages.add(((float)system_cellvoltages_mV[i]) / 1000.0);
}
// Publish and print error if not OK
if (mqtt_publish_retain("battery-emulator/spec_data") == false) {
Serial.println("Cell voltage MQTT msg could not be sent");
serializeJson(doc, mqtt_msg, sizeof(mqtt_msg));
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
Serial.println("Cell voltage MQTT msg could not be sent");
}
doc.clear();
}
}
struct SensorConfig {
const char* object_id;
const char* topic;
const char* name;
const char* value_template;
const char* unit;
@ -111,83 +101,69 @@ struct SensorConfig {
};
SensorConfig sensorConfigs[] = {
{"SOC", "homeassistant/sensor/battery-emulator/SOC/config", "Battery Emulator SOC", "{{ value_json.SOC }}", "%",
"battery"},
{"state_of_health", "homeassistant/sensor/battery-emulator/state_of_health/config",
"Battery Emulator State Of Health", "{{ value_json.state_of_health }}", "%", "battery"},
{"temperature_min", "homeassistant/sensor/battery-emulator/temperature_min/config",
"Battery Emulator Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"},
{"temperature_max", "homeassistant/sensor/battery-emulator/temperature_max/config",
"Battery Emulator Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"},
{"stat_batt_power", "homeassistant/sensor/battery-emulator/stat_batt_power/config",
"Battery Emulator Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"},
{"battery_current", "homeassistant/sensor/battery-emulator/battery_current/config",
"Battery Emulator Battery Current", "{{ value_json.battery_current }}", "A", "current"},
{"cell_max_voltage", "homeassistant/sensor/battery-emulator/cell_max_voltage/config",
"Battery Emulator Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"},
{"cell_min_voltage", "homeassistant/sensor/battery-emulator/cell_min_voltage/config",
"Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"},
{"battery_voltage", "homeassistant/sensor/battery-emulator/battery_voltage/config",
"Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"},
{"SOC", "Battery Emulator SOC", "{{ value_json.SOC }}", "%", "battery"},
{"state_of_health", "Battery Emulator State Of Health", "{{ value_json.state_of_health }}", "%", "battery"},
{"temperature_min", "Battery Emulator Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"},
{"temperature_max", "Battery Emulator Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"},
{"stat_batt_power", "Battery Emulator Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"},
{"battery_current", "Battery Emulator Battery Current", "{{ value_json.battery_current }}", "A", "current"},
{"cell_max_voltage", "Battery Emulator 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"},
{"battery_voltage", "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"},
};
static String generateCommonInfoAutoConfigTopic(const char* object_id, const char* hostname) {
return String("homeassistant/sensor/battery-emulator_") + String(hostname) + "/" + String(object_id) + "/config";
}
static void publish_common_info(void) {
static JsonDocument doc;
static bool mqtt_first_transmission = true;
static char* state_topic = "battery-emulator/info";
static const char* hostname = WiFi.getHostname();
static String state_topic = String("battery-emulator_") + String(hostname) + "/info";
if (mqtt_first_transmission == true) {
mqtt_first_transmission = false;
for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) {
SensorConfig& config = sensorConfigs[i];
snprintf(mqtt_msg, sizeof(mqtt_msg),
"{"
"\"name\": \"%s\","
"\"state_topic\": \"%s\","
"\"unique_id\": \"battery-emulator_%s\","
"\"object_id\": \"sensor_battery_%s\","
"\"device\": {"
"\"identifiers\": ["
"\"battery-emulator\""
"],"
"\"manufacturer\": \"DalaTech\","
"\"model\": \"BatteryEmulator\","
"\"name\": \"BatteryEmulator\""
"},"
"\"origin\": {"
"\"name\": \"BatteryEmulator\","
"\"sw\": \"%s-mqtt\","
"\"url\": \"https://github.com/dalathegreat/Battery-Emulator\""
"},"
"\"value_template\": \"%s\","
"\"unit_of_measurement\": \"%s\","
"\"device_class\": \"%s\","
"\"enabled_by_default\": true,"
"\"state_class\": \"measurement\""
"}",
config.name, state_topic, config.object_id, config.object_id, version_number, config.value_template,
config.unit, config.device_class);
mqtt_publish_retain(config.topic);
doc["name"] = config.name;
doc["state_topic"] = state_topic;
doc["unique_id"] = "battery-emulator_" + String(hostname) + "_" + String(config.object_id);
doc["object_id"] = String(hostname) + "_" + String(config.object_id);
doc["value_template"] = config.value_template;
doc["unit_of_measurement"] = config.unit;
doc["device_class"] = config.device_class;
doc["enabled_by_default"] = true;
doc["state_class"] = "measurement";
doc["expire_after"] = 240;
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);
mqtt_publish(generateCommonInfoAutoConfigTopic(config.object_id, hostname).c_str(), mqtt_msg, true);
}
doc.clear();
} else {
snprintf(mqtt_msg, sizeof(mqtt_msg),
"{\n"
" \"SOC\": %.3f,\n"
" \"state_of_health\": %.3f,\n"
" \"temperature_min\": %.3f,\n"
" \"temperature_max\": %.3f,\n"
" \"stat_batt_power\": %.3f,\n"
" \"battery_current\": %.3f,\n"
" \"cell_max_voltage\": %.3f,\n"
" \"cell_min_voltage\": %.3f,\n"
" \"battery_voltage\": %d\n"
"}\n",
((float)SOC) / 100.0, ((float)StateOfHealth) / 100.0, ((float)((int16_t)temperature_min)) / 10.0,
((float)((int16_t)temperature_max)) / 10.0, ((float)((int16_t)stat_batt_power)),
((float)((int16_t)battery_current)) / 10.0, ((float)cell_max_voltage) / 1000,
((float)cell_min_voltage) / 1000, battery_voltage / 10.0);
bool result = client.publish(state_topic, mqtt_msg, true);
}
doc["SOC"] = ((float)system_scaled_SOC_pptt) / 100.0;
doc["SOC_real"] = ((float)system_real_SOC_pptt) / 100.0;
doc["state_of_health"] = ((float)system_SOH_pptt) / 100.0;
doc["temperature_min"] = ((float)((int16_t)system_temperature_min_dC)) / 10.0;
doc["temperature_max"] = ((float)((int16_t)system_temperature_max_dC)) / 10.0;
doc["stat_batt_power"] = ((float)((int16_t)system_active_power_W));
doc["battery_current"] = ((float)((int16_t)system_battery_current_dA)) / 10.0;
doc["cell_max_voltage"] = ((float)system_cell_max_voltage_mV) / 1000.0;
doc["cell_min_voltage"] = ((float)system_cell_min_voltage_mV) / 1000.0;
doc["battery_voltage"] = ((float)system_battery_voltage_dV) / 10.0;
//Serial.println(mqtt_msg); // Uncomment to print the payload on serial
serializeJson(doc, mqtt_msg);
if (!mqtt_publish(state_topic.c_str(), mqtt_msg, false)) {
Serial.println("Common info MQTT msg could not be sent");
}
doc.clear();
}
}
/* This is called whenever a subscribed topic changes (hopefully) */
@ -206,9 +182,10 @@ static void reconnect() {
// attempt one reconnection
Serial.print("Attempting MQTT connection... ");
const char* hostname = WiFi.getHostname();
String clientId = "LilyGoClient-" + String(hostname);
char clientId[64]; // Adjust the size as needed
snprintf(clientId, sizeof(clientId), "LilyGoClient-%s", hostname);
// Attempt to connect
if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
if (client.connect(clientId, mqtt_user, mqtt_password)) {
Serial.println("connected");
for (int i = 0; i < mqtt_nof_subscriptions; i++) {
@ -238,20 +215,20 @@ void mqtt_loop(void) {
client.loop();
if (publish_global_timer.elapsed() == true) // Every 5s
{
publish_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus.
publish_values();
}
} else {
if (millis() - previousMillisUpdateVal >= 5000) // Every 5s
{
previousMillisUpdateVal = millis();
reconnect(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus.
reconnect();
}
}
}
bool mqtt_publish_retain(const char* topic) {
bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain) {
if (client.connected() == true) {
return client.publish(topic, mqtt_msg, true);
return client.publish(topic, mqtt_msg, retain);
}
return false;
}

View file

@ -41,16 +41,18 @@
extern const char* version_number; // The current software version, used for mqtt
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern uint8_t nof_cellvoltages; // Total number of cell voltages, set by each battery.
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000 , Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern const char* mqtt_user;
extern const char* mqtt_password;
@ -59,6 +61,6 @@ extern char mqtt_msg[MQTT_MSG_BUFFER_SIZE];
void init_mqtt(void);
void mqtt_loop(void);
bool mqtt_publish_retain(const char* topic);
bool mqtt_publish(const char* topic, const char* mqtt_msg, bool retain);
#endif

View file

@ -1,82 +1,190 @@
#include "events.h"
#ifndef UNIT_TEST
#include <EEPROM.h>
#endif
#include "../../../USER_SETTINGS.h"
#include "../config.h"
#include "timer.h"
unsigned long previous_millis = 0;
uint32_t time_seconds = 0;
static uint8_t total_led_color = GREEN;
static char event_message[256];
EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
#define EE_MAGIC_HEADER_VALUE 0xA5A5
#define EE_NOF_EVENT_ENTRIES 30
#define EE_EVENT_ENTRY_SIZE sizeof(EVENT_LOG_ENTRY_TYPE)
#define EE_WRITE_PERIOD_MINUTES 10
/** EVENT LOG STRUCTURE
*
* The event log is stored in a simple header-block structure. The
* header contains a magic number to identify it as an event log,
* a head index and a tail index. The head index points to the last
* recorded event, the tail index points to the "oldest" event in the
* log. The event log is set up like a circular buffer, so we only
* store the set amount of events. The head continuously overwrites
* the oldest events, and both the head and tail indices wrap around
* to 0 at the end of the event log:
*
* [ HEADER ]
* [ MAGIC NUMBER ][ HEAD INDEX ][ TAIL INDEX ][ EVENT BLOCK 0 ][ EVENT BLOCK 1]...
* [ 2 bytes ][ 2 bytes ][ 2 bytes ][ 6 bytes ][ 6 bytes ]
*
* 1024 bytes are allocated to the event log in flash emulated EEPROM,
* giving room for (1024 - (2 + 2 + 2)) / 6 ~= 169 events
*
* For now, we store 30 to make it easier to handle initial debugging.
*/
#define EE_EVENT_LOG_START_ADDRESS 0
#define EE_EVENT_LOG_HEAD_INDEX_ADDRESS EE_EVENT_LOG_START_ADDRESS + 2
#define EE_EVENT_LOG_TAIL_INDEX_ADDRESS EE_EVENT_LOG_HEAD_INDEX_ADDRESS + 2
#define EE_EVENT_ENTRY_START_ADDRESS EE_EVENT_LOG_TAIL_INDEX_ADDRESS + 2
typedef struct {
EVENTS_ENUM_TYPE event;
uint32_t timestamp;
uint8_t data;
} EVENT_LOG_ENTRY_TYPE;
typedef struct {
EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
uint32_t time_seconds;
MyTimer second_timer;
MyTimer ee_timer;
EVENTS_LEVEL_TYPE level;
uint16_t event_log_head_index;
uint16_t event_log_tail_index;
uint8_t nof_logged_events;
uint16_t nof_eeprom_writes;
} EVENT_TYPE;
/* Local variables */
static EVENT_TYPE events;
static const char* EVENTS_ENUM_TYPE_STRING[] = {EVENTS_ENUM_TYPE(GENERATE_STRING)};
static const char* EVENTS_LEVEL_TYPE_STRING[] = {EVENTS_LEVEL_TYPE(GENERATE_STRING)};
/* Local function prototypes */
static void set_event_message(EVENTS_ENUM_TYPE event);
static void update_led_color(EVENTS_ENUM_TYPE event);
static void update_event_time(void);
static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched);
static void update_event_level(void);
static void update_bms_status(void);
static void log_event(EVENTS_ENUM_TYPE event, uint8_t data);
static void print_event_log(void);
static void check_ee_write(void);
/* Exported functions */
/* Main execution function, should handle various continuous functionality */
void run_event_handling(void) {
update_event_time();
run_sequence_on_target();
check_ee_write();
}
/* Initialization function */
void init_events(void) {
for (uint8_t i = 0; i < EVENT_NOF_EVENTS; i++) {
entries[i].timestamp = 0;
entries[i].data = 0;
entries[i].occurences = 0;
entries[i].led_color = RED; // Most events are RED, critical errors
EEPROM.begin(1024);
events.nof_logged_events = 0;
uint16_t header = EEPROM.readUShort(EE_EVENT_LOG_START_ADDRESS);
if (header != EE_MAGIC_HEADER_VALUE) {
// The header doesn't appear to be a compatible event log, clear it and initialize
EEPROM.writeUShort(EE_EVENT_LOG_START_ADDRESS, EE_MAGIC_HEADER_VALUE);
EEPROM.writeUShort(EE_EVENT_LOG_HEAD_INDEX_ADDRESS, 0);
EEPROM.writeUShort(EE_EVENT_LOG_TAIL_INDEX_ADDRESS, 0);
// Prepare an empty event block to write
EVENT_LOG_ENTRY_TYPE entry = {.event = EVENT_NOF_EVENTS, .timestamp = 0, .data = 0};
// Put the event in (what I guess is) the RAM EEPROM mirror, or write buffer
for (int i = 0; i < EE_NOF_EVENT_ENTRIES; i++) {
// Start at the oldest event, work through the log all the way the the head
int address = EE_EVENT_ENTRY_START_ADDRESS + EE_EVENT_ENTRY_SIZE * i;
EEPROM.put(address, entry);
}
// Push changes to eeprom
EEPROM.commit();
Serial.println("EEPROM wasn't ready");
} else {
events.event_log_head_index = EEPROM.readUShort(EE_EVENT_LOG_HEAD_INDEX_ADDRESS);
events.event_log_tail_index = EEPROM.readUShort(EE_EVENT_LOG_TAIL_INDEX_ADDRESS);
Serial.println("EEPROM was initialized for event logging");
Serial.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index));
print_event_log();
}
// YELLOW warning events below
entries[EVENT_12V_LOW].led_color = YELLOW;
entries[EVENT_CAN_WARNING].led_color = YELLOW;
entries[EVENT_CELL_DEVIATION_HIGH].led_color = YELLOW;
entries[EVENT_KWH_PLAUSIBILITY_ERROR].led_color = YELLOW;
for (uint16_t i = 0; i < EVENT_NOF_EVENTS; i++) {
events.entries[i].data = 0;
events.entries[i].timestamp = 0;
events.entries[i].occurences = 0;
events.entries[i].log = true;
}
events.entries[EVENT_CAN_RX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_12V_LOW].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_KWH_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_EMPTY].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_FULL].level = EVENT_LEVEL_INFO;
events.entries[EVENT_BATTERY_CHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_BATTERY_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_BATTERY_CHG_DISCHG_STOP_REQ].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_LOW_SOH].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_HVIL_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_INTERNAL_OPEN_FAULT].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_INVERTER_OPEN_CONTACTOR].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CELL_UNDER_VOLTAGE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CELL_OVER_VOLTAGE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CELL_DEVIATION_HIGH].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_UNKNOWN_EVENT_SET].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_OTA_UPDATE].level = EVENT_LEVEL_UPDATE;
events.entries[EVENT_OTA_UPDATE_TIMEOUT].level = EVENT_LEVEL_INFO;
events.entries[EVENT_DUMMY_INFO].level = EVENT_LEVEL_INFO;
events.entries[EVENT_DUMMY_DEBUG].level = EVENT_LEVEL_DEBUG;
events.entries[EVENT_DUMMY_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_DUMMY_ERROR].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_SERIAL_RX_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_SERIAL_RX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_SERIAL_TX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_SERIAL_TRANSMITTER_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_EEPROM_WRITE].level = EVENT_LEVEL_INFO;
events.entries[EVENT_EEPROM_WRITE].log = false; // Don't log the logger...
events.second_timer.set_interval(1000);
// Write to EEPROM every X minutes (if an event has been set)
events.ee_timer.set_interval(EE_WRITE_PERIOD_MINUTES * 60 * 1000);
}
void set_event(EVENTS_ENUM_TYPE event, uint8_t data) {
if (event >= EVENT_NOF_EVENTS) {
event = EVENT_UNKNOWN_EVENT_SET;
}
entries[event].timestamp = time_seconds;
entries[event].data = data;
++entries[event].occurences;
set_event_message(event);
#ifdef DEBUG_VIA_USB
Serial.println("Set event: " + String(get_event_enum_string(event)) + ". Has occured " +
String(entries[event].occurences) + " times");
#endif
set_event(event, data, false);
}
void update_event_timestamps(void) {
unsigned long new_millis = millis();
if (new_millis - previous_millis >= 1000) {
time_seconds++;
previous_millis = new_millis;
void set_event_latched(EVENTS_ENUM_TYPE event, uint8_t data) {
set_event(event, data, true);
}
void clear_event(EVENTS_ENUM_TYPE event) {
if (events.entries[event].state == EVENT_STATE_ACTIVE) {
events.entries[event].state = EVENT_STATE_INACTIVE;
update_event_level();
update_bms_status();
}
}
/* Local functions */
static void update_led_color(EVENTS_ENUM_TYPE event) {
total_led_color = (total_led_color == RED) ? RED : entries[event].led_color;
}
const char* get_led_color_display_text(u_int8_t led_color) {
switch (led_color) {
case RED:
return "Error";
case YELLOW:
return "Warning";
case GREEN:
return "Info";
case BLUE:
return "Debug";
default:
return "UNKNOWN";
}
}
const char* get_event_message(EVENTS_ENUM_TYPE event) {
const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
switch (event) {
case EVENT_CAN_FAILURE:
case EVENT_CAN_RX_FAILURE:
return "No CAN communication detected for 60s. Shutting down battery control.";
case EVENT_CAN_WARNING:
case EVENT_CAN_RX_WARNING:
return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!";
case EVENT_CAN_TX_FAILURE:
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
case EVENT_WATER_INGRESS:
return "Water leakage inside battery detected. Operation halted. Inspect battery!";
case EVENT_12V_LOW:
@ -84,7 +192,11 @@ const char* get_event_message(EVENTS_ENUM_TYPE event) {
case EVENT_SOC_PLAUSIBILITY_ERROR:
return "ERROR: SOC% reported by battery not plausible. Restart battery!";
case EVENT_KWH_PLAUSIBILITY_ERROR:
return "Warning: kWh remaining reported by battery not plausible. Battery needs cycling.";
return "Info: kWh remaining reported by battery not plausible. Battery needs cycling.";
case EVENT_BATTERY_EMPTY:
return "Info: Battery is completely discharged";
case EVENT_BATTERY_FULL:
return "Info: Battery is fully charged";
case EVENT_BATTERY_CHG_STOP_REQ:
return "ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!";
case EVENT_BATTERY_DISCHG_STOP_REQ:
@ -99,6 +211,8 @@ const char* get_event_message(EVENTS_ENUM_TYPE event) {
"disabled!";
case EVENT_INTERNAL_OPEN_FAULT:
return "ERROR: High voltage cable removed while battery running. Opening contactors!";
case EVENT_INVERTER_OPEN_CONTACTOR:
return "ERROR: Inverter requested contactors to open. Opening contactors!";
case EVENT_CELL_UNDER_VOLTAGE:
return "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!";
case EVENT_CELL_OVER_VOLTAGE:
@ -107,22 +221,187 @@ const char* get_event_message(EVENTS_ENUM_TYPE event) {
return "ERROR: HIGH CELL DEVIATION!!! Inspect battery!";
case EVENT_UNKNOWN_EVENT_SET:
return "An unknown event was set! Review your code!";
case EVENT_DUMMY:
return "The dummy event was set!"; // Don't change this event message!
case EVENT_DUMMY_INFO:
return "The dummy info event was set!"; // Don't change this event message!
case EVENT_DUMMY_DEBUG:
return "The dummy debug event was set!"; // Don't change this event message!
case EVENT_DUMMY_WARNING:
return "The dummy warning event was set!"; // Don't change this event message!
case EVENT_DUMMY_ERROR:
return "The dummy error event was set!"; // Don't change this event message!
case EVENT_SERIAL_RX_WARNING:
return "Error in serial function: No data received for some time, see data for minutes";
case EVENT_SERIAL_RX_FAILURE:
return "Error in serial function: No data for a long time!";
case EVENT_SERIAL_TX_FAILURE:
return "Error in serial function: No ACK from receiver!";
case EVENT_SERIAL_TRANSMITTER_FAILURE:
return "Error in serial function: Some ERROR level fault in transmitter, received by receiver";
case EVENT_OTA_UPDATE:
return "OTA update started!";
case EVENT_OTA_UPDATE_TIMEOUT:
return "OTA update timed out!";
case EVENT_EEPROM_WRITE:
return "Info: The EEPROM was written";
default:
return "";
}
}
const char* get_event_enum_string(EVENTS_ENUM_TYPE event) {
const char* fullString = EVENTS_ENUM_TYPE_STRING[event];
if (strncmp(fullString, "EVENT_", 6) == 0) {
return fullString + 6; // Skip the first 6 characters
}
return fullString;
// Return the event name but skip "EVENT_" that should always be first
return EVENTS_ENUM_TYPE_STRING[event] + 6;
}
static void set_event_message(EVENTS_ENUM_TYPE event) {
const char* message = get_event_message(event);
snprintf(event_message, sizeof(event_message), "%s", message);
const char* get_event_level_string(EVENTS_ENUM_TYPE event) {
// Return the event level but skip "EVENT_LEVEL_" that should always be first
return EVENTS_LEVEL_TYPE_STRING[events.entries[event].level] + 12;
}
const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event) {
return &events.entries[event];
}
EVENTS_LEVEL_TYPE get_event_level(void) {
return events.level;
}
/* Local functions */
static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) {
// Just some defensive stuff if someone sets an unknown event
if (event >= EVENT_NOF_EVENTS) {
event = EVENT_UNKNOWN_EVENT_SET;
}
// If the event is already set, no reason to continue
if ((events.entries[event].state != EVENT_STATE_ACTIVE) &&
(events.entries[event].state != EVENT_STATE_ACTIVE_LATCHED)) {
events.entries[event].occurences++;
if (events.entries[event].log) {
log_event(event, data);
}
}
// We should set the event, update event info
events.entries[event].timestamp = events.time_seconds;
events.entries[event].data = data;
// Check if the event is latching
events.entries[event].state = latched ? EVENT_STATE_ACTIVE_LATCHED : EVENT_STATE_ACTIVE;
update_event_level();
update_bms_status();
#ifdef DEBUG_VIA_USB
Serial.println(get_event_message_string(event));
#endif
}
static void update_bms_status(void) {
switch (events.level) {
case EVENT_LEVEL_INFO:
case EVENT_LEVEL_WARNING:
case EVENT_LEVEL_DEBUG:
system_bms_status = ACTIVE;
break;
case EVENT_LEVEL_UPDATE:
system_bms_status = UPDATING;
break;
case EVENT_LEVEL_ERROR:
system_bms_status = FAULT;
break;
default:
break;
}
}
static void update_event_level(void) {
events.level = EVENT_LEVEL_INFO;
for (uint8_t i = 0u; i < EVENT_NOF_EVENTS; i++) {
if ((events.entries[i].state == EVENT_STATE_ACTIVE) || (events.entries[i].state == EVENT_STATE_ACTIVE_LATCHED)) {
events.level = max(events.entries[i].level, events.level);
}
}
}
static void update_event_time(void) {
if (events.second_timer.elapsed() == true) {
events.time_seconds++;
}
}
static void log_event(EVENTS_ENUM_TYPE event, uint8_t data) {
// Update head with wrap to 0
if (++events.event_log_head_index == EE_NOF_EVENT_ENTRIES) {
events.event_log_head_index = 0;
}
// If the head now points to the tail, move the tail, with wrap to 0
if (events.event_log_head_index == events.event_log_tail_index) {
if (++events.event_log_tail_index == EE_NOF_EVENT_ENTRIES) {
events.event_log_tail_index = 0;
}
}
// The head now holds the index to the oldest event, the one we want to overwrite,
// so calculate the absolute address
int entry_address = EE_EVENT_ENTRY_START_ADDRESS + EE_EVENT_ENTRY_SIZE * events.event_log_head_index;
// Prepare an event block to write
EVENT_LOG_ENTRY_TYPE entry = {.event = event, .timestamp = events.time_seconds, .data = data};
// Put the event in (what I guess is) the RAM EEPROM mirror, or write buffer
EEPROM.put(entry_address, entry);
// Store the new indices
EEPROM.writeUShort(EE_EVENT_LOG_HEAD_INDEX_ADDRESS, events.event_log_head_index);
EEPROM.writeUShort(EE_EVENT_LOG_TAIL_INDEX_ADDRESS, events.event_log_tail_index);
//Serial.println("Wrote event " + String(event) + " to " + String(entry_address));
//Serial.println("head: " + String(events.event_log_head_index) + ", tail: " + String(events.event_log_tail_index));
// We don't need the exact number, it's just for deciding to store or not
events.nof_logged_events += (events.nof_logged_events < 255) ? 1 : 0;
}
static void print_event_log(void) {
// If the head actually points to the tail, the log is probably blank
if (events.event_log_head_index == events.event_log_tail_index) {
Serial.println("No events in log");
return;
}
EVENT_LOG_ENTRY_TYPE entry;
for (int i = 0; i < EE_NOF_EVENT_ENTRIES; i++) {
// Start at the oldest event, work through the log all the way the the head
int index = ((events.event_log_tail_index + i) % EE_NOF_EVENT_ENTRIES);
int address = EE_EVENT_ENTRY_START_ADDRESS + EE_EVENT_ENTRY_SIZE * index;
EEPROM.get(address, entry);
if (entry.event == EVENT_NOF_EVENTS) {
// The entry is a blank that has been left behind somehow
continue;
}
Serial.println("Event: " + String(get_event_enum_string(entry.event)) + ", data: " + String(entry.data) +
", time: " + String(entry.timestamp));
if (index == events.event_log_head_index) {
break;
}
}
}
static void check_ee_write(void) {
// Only actually write to flash emulated EEPROM every EE_WRITE_PERIOD_MINUTES minutes,
// and only if we've logged any events
if (events.ee_timer.elapsed() && (events.nof_logged_events > 0)) {
EEPROM.commit();
events.nof_eeprom_writes += (events.nof_eeprom_writes < 65535) ? 1 : 0;
events.nof_logged_events = 0;
// We want to know how many writes we have, and to increment the occurrence counter
// we need to clear it first. It's just the way life is. Using events is a smooth
// way to visualize it in the web UI
clear_event(EVENT_EEPROM_WRITE);
set_event(EVENT_EEPROM_WRITE, events.nof_eeprom_writes);
}
}

View file

@ -4,52 +4,102 @@
#ifndef UNIT_TEST
#include <Arduino.h>
extern unsigned long previous_millis;
extern uint32_t time_seconds;
#endif
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
/** EVENT ENUMERATION
*
* Do not change the order!
* When adding events, add them RIGHT BEFORE the EVENT_NOF_EVENTS enum.
* In addition, the event name must start with "EVENT_"
*
* After adding an event, assign the proper event level in events.cpp:init_events()
*/
#define EVENTS_ENUM_TYPE(XX) \
XX(EVENT_CAN_FAILURE) \
XX(EVENT_CAN_WARNING) \
XX(EVENT_CAN_RX_FAILURE) \
XX(EVENT_CAN_RX_WARNING) \
XX(EVENT_CAN_TX_FAILURE) \
XX(EVENT_WATER_INGRESS) \
XX(EVENT_12V_LOW) \
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
XX(EVENT_BATTERY_EMPTY) \
XX(EVENT_BATTERY_FULL) \
XX(EVENT_BATTERY_CHG_STOP_REQ) \
XX(EVENT_BATTERY_DISCHG_STOP_REQ) \
XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \
XX(EVENT_LOW_SOH) \
XX(EVENT_HVIL_FAILURE) \
XX(EVENT_INTERNAL_OPEN_FAULT) \
XX(EVENT_INVERTER_OPEN_CONTACTOR) \
XX(EVENT_CELL_UNDER_VOLTAGE) \
XX(EVENT_CELL_OVER_VOLTAGE) \
XX(EVENT_CELL_DEVIATION_HIGH) \
XX(EVENT_UNKNOWN_EVENT_SET) \
XX(EVENT_DUMMY) \
XX(EVENT_OTA_UPDATE) \
XX(EVENT_OTA_UPDATE_TIMEOUT) \
XX(EVENT_DUMMY_INFO) \
XX(EVENT_DUMMY_DEBUG) \
XX(EVENT_DUMMY_WARNING) \
XX(EVENT_DUMMY_ERROR) \
XX(EVENT_SERIAL_RX_WARNING) \
XX(EVENT_SERIAL_RX_FAILURE) \
XX(EVENT_SERIAL_TX_FAILURE) \
XX(EVENT_SERIAL_TRANSMITTER_FAILURE) \
XX(EVENT_EEPROM_WRITE) \
XX(EVENT_NOF_EVENTS)
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;
static const char* EVENTS_ENUM_TYPE_STRING[] = {EVENTS_ENUM_TYPE(GENERATE_STRING)};
/* Event type enumeration, keep in order of priority! */
#define EVENTS_LEVEL_TYPE(XX) \
XX(EVENT_LEVEL_INFO) \
XX(EVENT_LEVEL_DEBUG) \
XX(EVENT_LEVEL_WARNING) \
XX(EVENT_LEVEL_ERROR) \
XX(EVENT_LEVEL_UPDATE)
typedef enum { EVENTS_LEVEL_TYPE(GENERATE_ENUM) } EVENTS_LEVEL_TYPE;
typedef enum {
EVENT_STATE_PENDING = 0,
EVENT_STATE_INACTIVE,
EVENT_STATE_ACTIVE,
EVENT_STATE_ACTIVE_LATCHED
} EVENTS_STATE_TYPE;
typedef struct {
uint32_t timestamp; // Time in seconds since startup when the event occurred
uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage
uint8_t occurences; // Number of occurrences since startup
EVENTS_LEVEL_TYPE level; // Event level, i.e. ERROR/WARNING...
EVENTS_STATE_TYPE state; // Event state, i.e. ACTIVE/INACTIVE...
bool log;
} EVENTS_STRUCT_TYPE;
const char* get_event_enum_string(EVENTS_ENUM_TYPE event);
const char* get_event_message_string(EVENTS_ENUM_TYPE event);
const char* get_event_level_string(EVENTS_ENUM_TYPE event);
const char* get_event_type(EVENTS_ENUM_TYPE event);
const char* get_event_message(EVENTS_ENUM_TYPE event);
const char* get_led_color_display_text(u_int8_t led_color);
EVENTS_LEVEL_TYPE get_event_level(void);
void init_events(void);
void set_event_latched(EVENTS_ENUM_TYPE event, uint8_t data);
void set_event(EVENTS_ENUM_TYPE event, uint8_t data);
void update_event_timestamps(void);
typedef struct {
uint32_t timestamp; // Time in seconds since startup when the event occurred
uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage
uint8_t occurences; // Number of occurrences since startup
uint8_t led_color; // Weirdly indented comment
} EVENTS_STRUCT_TYPE;
extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
void clear_event(EVENTS_ENUM_TYPE event);
const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event);
void run_event_handling(void);
void run_sequence_on_target(void);
extern uint8_t system_bms_status; //Enum 0-5
#endif // __MYTIMER_H__

View file

@ -0,0 +1,142 @@
#include "events.h"
#include "timer.h"
typedef enum {
ETOT_INIT,
ETOT_FIRST_WAIT,
ETOT_INFO,
ETOT_INFO_CLEAR,
ETOT_DEBUG,
ETOT_DEBUG_CLEAR,
ETOT_WARNING,
ETOT_WARNING_CLEAR,
ETOT_ERROR,
ETOT_ERROR_CLEAR,
ETOT_ERROR_LATCHED,
ETOT_DONE
} ETOT_TYPE;
MyTimer timer(5000);
ETOT_TYPE events_test_state = ETOT_INIT;
void run_sequence_on_target(void) {
#ifdef INCLUDE_EVENTS_TEST
switch (events_test_state) {
case ETOT_INIT:
timer.set_interval(10000);
events_test_state = ETOT_FIRST_WAIT;
Serial.println("Events test: initialized");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
break;
case ETOT_FIRST_WAIT:
if (timer.elapsed()) {
timer.set_interval(8000);
events_test_state = ETOT_INFO;
set_event(EVENT_DUMMY_INFO, 123);
set_event(EVENT_DUMMY_INFO, 234); // 234 should show, occurrence 1
Serial.println("Events test: info event set, data: 234");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
}
break;
case ETOT_INFO:
if (timer.elapsed()) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_INFO);
events_test_state = ETOT_INFO_CLEAR;
Serial.println("Events test : info event cleared");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
}
break;
case ETOT_INFO_CLEAR:
if (timer.elapsed()) {
timer.set_interval(8000);
events_test_state = ETOT_DEBUG;
set_event(EVENT_DUMMY_DEBUG, 111);
set_event(EVENT_DUMMY_DEBUG, 222); // 222 should show, occurrence 1
Serial.println("Events test : debug event set, data: 222");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
}
break;
case ETOT_DEBUG:
if (timer.elapsed()) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_DEBUG);
events_test_state = ETOT_DEBUG_CLEAR;
Serial.println("Events test : info event cleared");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
}
break;
case ETOT_DEBUG_CLEAR:
if (timer.elapsed()) {
timer.set_interval(8000);
events_test_state = ETOT_WARNING;
set_event(EVENT_DUMMY_WARNING, 234);
set_event(EVENT_DUMMY_WARNING, 121); // 121 should show, occurrence 1
Serial.println("Events test : warning event set, data: 121");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
}
break;
case ETOT_WARNING:
if (timer.elapsed()) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_WARNING);
events_test_state = ETOT_WARNING_CLEAR;
Serial.println("Events test : warning event cleared");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
}
break;
case ETOT_WARNING_CLEAR:
if (timer.elapsed()) {
timer.set_interval(8000);
events_test_state = ETOT_ERROR;
set_event(EVENT_DUMMY_ERROR, 221);
set_event(EVENT_DUMMY_ERROR, 133); // 133 should show, occurrence 1
Serial.println("Events test : error event set, data: 133");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
}
break;
case ETOT_ERROR:
if (timer.elapsed()) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_ERROR);
events_test_state = ETOT_ERROR_CLEAR;
Serial.println("Events test : error event cleared");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
}
break;
case ETOT_ERROR_CLEAR:
if (timer.elapsed()) {
timer.set_interval(8000);
events_test_state = ETOT_ERROR_LATCHED;
set_event_latched(EVENT_DUMMY_ERROR, 221);
set_event_latched(EVENT_DUMMY_ERROR, 133); // 133 should show, occurrence 1
Serial.println("Events test : latched error event set, data: 133");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
}
break;
case ETOT_ERROR_LATCHED:
if (timer.elapsed()) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_ERROR);
events_test_state = ETOT_DONE;
Serial.println("Events test : latched error event cleared?");
Serial.print("system_bms_status: ");
Serial.println(system_bms_status);
}
break;
case ETOT_DONE:
default:
break;
}
#endif
}

View file

@ -4,7 +4,7 @@ MyTimer::MyTimer(unsigned long interval) : interval(interval) {
previous_millis = millis();
}
bool MyTimer::elapsed() {
bool MyTimer::elapsed(void) {
unsigned long current_millis = millis();
if (current_millis - previous_millis >= interval) {
previous_millis = current_millis;
@ -12,3 +12,12 @@ bool MyTimer::elapsed() {
}
return false;
}
void MyTimer::reset(void) {
previous_millis = millis();
}
void MyTimer::set_interval(unsigned long interval) {
this->interval = interval;
reset();
}

View file

@ -7,14 +7,20 @@
class MyTimer {
public:
/** Default constructor */
MyTimer() : interval(0), previous_millis(0) {}
/** interval in ms */
MyTimer(unsigned long interval);
/** Returns true and resets the timer if it has elapsed */
bool elapsed();
bool elapsed(void);
void reset(void);
void set_interval(unsigned long interval);
private:
unsigned long interval;
unsigned long previous_millis;
private:
};
#endif // __MYTIMER_H__

View file

@ -18,9 +18,9 @@ String cellmonitor_processor(const String& var) {
// Display max, min, and deviation voltage values
content += "<div class='voltage-values'>";
content += "Max Voltage: " + String(cell_max_voltage) + " mV<br>";
content += "Min Voltage: " + String(cell_min_voltage) + " mV<br>";
int deviation = cell_max_voltage - cell_min_voltage;
content += "Max Voltage: " + String(system_cell_max_voltage_mV) + " mV<br>";
content += "Min Voltage: " + String(system_cell_min_voltage_mV) + " mV<br>";
int deviation = system_cell_max_voltage_mV - system_cell_min_voltage_mV;
content += "Voltage Deviation: " + String(deviation) + " mV";
content += "</div>";
@ -28,14 +28,14 @@ String cellmonitor_processor(const String& var) {
content += "<div class='container'>";
for (int i = 0; i < 120; ++i) {
// Skip empty values
if (cellvoltages[i] == 0) {
if (system_cellvoltages_mV[i] == 0) {
continue;
}
String cellContent = "Cell " + String(i + 1) + "<br>" + String(cellvoltages[i]) + " mV";
String cellContent = "Cell " + String(i + 1) + "<br>" + String(system_cellvoltages_mV[i]) + " mV";
// Check if the cell voltage is below 3000, apply red color
if (cellvoltages[i] < 3000) {
if (system_cellvoltages_mV[i] < 3000) {
cellContent = "<span class='low-voltage'>" + cellContent + "</span>";
}

View file

@ -4,9 +4,9 @@
#include <Arduino.h>
#include <stdint.h>
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
/**
* @brief Replaces placeholder with content section in web page

View file

@ -1,5 +1,6 @@
#include "events_html.h"
#include <Arduino.h>
#include "../utils/events.h"
const char EVENTS_HTML_START[] = R"=====(
<style>body{background-color:#000;color:#fff}.event-log{display:flex;flex-direction:column}.event{display:flex;flex-wrap:wrap;border:1px solid #fff;padding:10px}.event>div{flex:1;min-width:100px;max-width:90%;word-break:break-word}</style><div style="background-color:#303e47;padding:10px;margin-bottom:10px;border-radius:25px"><div class="event-log"><div class="event" style="background-color:#1e2c33;font-weight:700"><div>Event Type</div><div>Severity</div><div>Last Event</div><div>Count</div><div>Data</div><div>Message</div></div>
@ -17,18 +18,22 @@ String events_processor(const String& var) {
content.reserve(5000);
// Page format
content.concat(FPSTR(EVENTS_HTML_START));
const EVENTS_STRUCT_TYPE* event_pointer;
for (int i = 0; i < EVENT_NOF_EVENTS; i++) {
Serial.println("Event: " + String(get_event_enum_string(static_cast<EVENTS_ENUM_TYPE>(i))) +
" count: " + String(entries[i].occurences) + " seconds: " + String(entries[i].timestamp) +
" data: " + String(entries[i].data));
if (entries[i].occurences > 0) {
event_pointer = get_event_pointer((EVENTS_ENUM_TYPE)i);
EVENTS_ENUM_TYPE event_handle = static_cast<EVENTS_ENUM_TYPE>(i);
Serial.println("Event: " + String(get_event_enum_string(event_handle)) +
" count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) +
" data: " + String(event_pointer->data) +
" level: " + String(get_event_level_string(event_handle)));
if (event_pointer->occurences > 0) {
content.concat("<div class='event'>");
content.concat("<div>" + String(get_event_enum_string(static_cast<EVENTS_ENUM_TYPE>(i))) + "</div>");
content.concat("<div>" + String(get_led_color_display_text(entries[i].led_color)) + "</div>");
content.concat("<div class='last-event-seconds-ago'>" + String(entries[i].timestamp) + "</div>");
content.concat("<div>" + String(entries[i].occurences) + "</div>");
content.concat("<div>" + String(entries[i].data) + "</div>");
content.concat("<div>" + String(get_event_message(static_cast<EVENTS_ENUM_TYPE>(i))) + "</div>");
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>");
content.concat("<div class='last-event-seconds-ago'>" + String(event_pointer->timestamp) + "</div>");
content.concat("<div>" + String(event_pointer->occurences) + "</div>");
content.concat("<div>" + String(event_pointer->data) + "</div>");
content.concat("<div>" + String(get_event_message_string(event_handle)) + "</div>");
content.concat("<div class='timestamp' style='display:none;'>" + String(millis() / 1000) + "</div>");
content.concat("</div>"); // End of event row
}

View file

@ -1,9 +1,7 @@
#ifndef EVENTS_H
#define EVENTS_H
#include "../utils/events.h"
extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
#include <Arduino.h>
/**
* @brief Replaces placeholder with content section in web page

View file

@ -15,6 +15,8 @@ String settings_processor(const String& var) {
// Show current settings with edit buttons and input fields
content += "<h4 style='color: white;'>Battery capacity: <span id='BATTERY_WH_MAX'>" + String(BATTERY_WH_MAX) +
" Wh </span> <button onclick='editWh()'>Edit</button></h4>";
content += "<h4 style='color: white;'>Rescale SOC: <span id='USE_SCALED_SOC'>" + String(USE_SCALED_SOC) +
"</span> <button onclick='editUseScaledSOC()'>Edit</button></h4>";
content += "<h4 style='color: white;'>SOC max percentage: " + String(MAXPERCENTAGE / 10.0, 1) +
" </span> <button onclick='editSocMax()'>Edit</button></h4>";
content += "<h4 style='color: white;'>SOC min percentage: " + String(MINPERCENTAGE / 10.0, 1) +
@ -29,7 +31,7 @@ String settings_processor(const String& var) {
#ifdef TEST_FAKE_BATTERY
// Start a new block with blue background color
content += "<div style='background-color: #2E37AD; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
float voltageFloat = static_cast<float>(battery_voltage) / 10.0; // Convert to float and divide by 10
float voltageFloat = static_cast<float>(system_battery_voltage_dV) / 10.0; // Convert to float and divide by 10
content += "<h4 style='color: white;'>Fake battery voltage: " + String(voltageFloat, 1) +
" V </span> <button onclick='editFakeBatteryVoltage()'>Edit</button></h4>";
@ -69,14 +71,26 @@ String settings_processor(const String& var) {
content += "<script>";
content += "function editWh() {";
content += "var value = prompt('How much energy the battery can store. Enter new Wh value (1-65000):');";
content += "var value = prompt('How much energy the battery can store. Enter new Wh value (1-120000):');";
content += "if (value !== null) {";
content += " if (value >= 1 && value <= 65000) {";
content += " if (value >= 1 && value <= 120000) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateBatterySize?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 1 and 65000.');";
content += " alert('Invalid value. Please enter a value between 1 and 120000.');";
content += " }";
content += "}";
content += "}";
content += "function editUseScaledSOC() {";
content += "var value = prompt('Should SOC% be scaled? (0 = No, 1 = Yes):');";
content += "if (value !== null) {";
content += " if (value == 0 || value == 1) {";
content += " var xhr = new XMLHttpRequest();";
content += " xhr.open('GET', '/updateUseScaledSOC?value=' + value, true);";
content += " xhr.send();";
content += " } else {";
content += " alert('Invalid value. Please enter a value between 0 and 1.');";
content += " }";
content += "}";
content += "}";

View file

@ -3,8 +3,8 @@
#include <Arduino.h>
#include "../../../USER_SETTINGS.h" // Needed for WiFi ssid and password
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
#include "../../../USER_SETTINGS.h" // Needed for WiFi ssid and password
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
/**
* @brief Replaces placeholder with content section in web page

View file

@ -1,5 +1,7 @@
#include "webserver.h"
#include <Preferences.h>
#include "../utils/events.h"
#include "../utils/timer.h"
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
@ -20,6 +22,9 @@ enum WifiState {
WifiState wifi_state = INIT;
MyTimer ota_timeout_timer = MyTimer(5000);
bool ota_active = false;
unsigned const long WIFI_MONITOR_INTERVAL_TIME = 15000;
unsigned const long INIT_WIFI_CONNECT_TIMEOUT = 8000; // Timeout for initial WiFi connect in milliseconds
unsigned const long DEFAULT_WIFI_RECONNECT_INTERVAL = 1000; // Default WiFi reconnect interval in ms
@ -69,6 +74,18 @@ void init_webserver() {
}
});
// Route for editing USE_SCALED_SOC
server.on("/updateUseScaledSOC", HTTP_GET, [](AsyncWebServerRequest* request) {
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
USE_SCALED_SOC = value.toInt();
storeSettings();
request->send(200, "text/plain", "Updated successfully");
} else {
request->send(400, "text/plain", "Bad Request");
}
});
// Route for editing SOCMax
server.on("/updateSocMax", HTTP_GET, [](AsyncWebServerRequest* request) {
if (request->hasParam("value")) {
@ -127,7 +144,7 @@ void init_webserver() {
String value = request->getParam("value")->value();
float val = value.toFloat();
battery_voltage = val * 10;
system_battery_voltage_dV = val * 10;
request->send(200, "text/plain", "Updated successfully");
});
@ -298,6 +315,14 @@ void wifi_monitor() {
Serial.println(" Hostname: " + String(WiFi.getHostname()));
}
}
if (ota_active && ota_timeout_timer.elapsed()) {
// OTA timeout, try to restore can and clear the update event
ESP32Can.CANInit();
clear_event(EVENT_OTA_UPDATE);
set_event(EVENT_OTA_UPDATE_TIMEOUT, 0);
ota_active = false;
}
}
void init_WiFi_STA(const char* ssid, const char* password, const uint8_t wifi_channel) {
@ -467,57 +492,42 @@ String processor(const String& var) {
}
// Display battery statistics within this block
float socFloat = static_cast<float>(SOC) / 100.0; // Convert to float and divide by 100
float sohFloat = static_cast<float>(StateOfHealth) / 100.0; // Convert to float and divide by 100
float voltageFloat = static_cast<float>(battery_voltage) / 10.0; // Convert to float and divide by 10
float currentFloat = 0;
if (battery_current > 32767) { //Handle negative values on this unsigned value
currentFloat = static_cast<float>(-(65535 - battery_current)) / 10.0; // Convert to float and divide by 10
} else {
currentFloat = static_cast<float>(battery_current) / 10.0; // Convert to float and divide by 10
}
float powerFloat = 0;
if (stat_batt_power > 32767) { //Handle negative values on this unsigned value
powerFloat = static_cast<float>(-(65535 - stat_batt_power));
} else {
powerFloat = static_cast<float>(stat_batt_power);
}
float tempMaxFloat = 0;
float tempMinFloat = 0;
if (temperature_max > 32767) { //Handle negative values on this unsigned value
tempMaxFloat = static_cast<float>(-(65536 - temperature_max)) / 10.0; // Convert to float and divide by 10
} else {
tempMaxFloat = static_cast<float>(temperature_max) / 10.0; // Convert to float and divide by 10
}
if (temperature_min > 32767) { //Handle negative values on this unsigned value
tempMinFloat = static_cast<float>(-(65536 - temperature_min)) / 10.0; // Convert to float and divide by 10
} else {
tempMinFloat = static_cast<float>(temperature_min) / 10.0; // Convert to float and divide by 10
}
content += "<h4 style='color: white;'>SOC: " + String(socFloat, 2) + "</h4>";
float socRealFloat = static_cast<float>(system_real_SOC_pptt) / 100.0; // Convert to float and divide by 100
float socScaledFloat = static_cast<float>(system_scaled_SOC_pptt) / 100.0; // Convert to float and divide by 100
float sohFloat = static_cast<float>(system_SOH_pptt) / 100.0; // Convert to float and divide by 100
float voltageFloat = static_cast<float>(system_battery_voltage_dV) / 10.0; // Convert to float and divide by 10
float currentFloat = static_cast<float>(system_battery_current_dA) / 10.0; // Convert to float and divide by 10
float powerFloat = static_cast<float>(system_active_power_W); // Convert to float
float tempMaxFloat = static_cast<float>(system_temperature_max_dC) / 10.0; // Convert to float
float tempMinFloat = static_cast<float>(system_temperature_min_dC) / 10.0; // Convert to float
content += "<h4 style='color: white;'>Real SOC: " + String(socRealFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>Scaled SOC: " + String(socScaledFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>SOH: " + String(sohFloat, 2) + "</h4>";
content += "<h4 style='color: white;'>Voltage: " + String(voltageFloat, 1) + " V</h4>";
content += "<h4 style='color: white;'>Current: " + String(currentFloat, 1) + " A</h4>";
content += formatPowerValue("Power", powerFloat, "", 1);
content += formatPowerValue("Total capacity", capacity_Wh, "h", 0);
content += formatPowerValue("Remaining capacity", remaining_capacity_Wh, "h", 1);
content += formatPowerValue("Max discharge power", max_target_discharge_power, "", 1);
content += formatPowerValue("Max charge power", max_target_charge_power, "", 1);
content += "<h4>Cell max: " + String(cell_max_voltage) + " mV</h4>";
content += "<h4>Cell min: " + String(cell_min_voltage) + " mV</h4>";
content += formatPowerValue("Total capacity", system_capacity_Wh, "h", 0);
content += formatPowerValue("Remaining capacity", system_remaining_capacity_Wh, "h", 1);
content += formatPowerValue("Max discharge power", system_max_discharge_power_W, "", 1);
content += formatPowerValue("Max charge power", system_max_charge_power_W, "", 1);
content += "<h4>Cell max: " + String(system_cell_max_voltage_mV) + " mV</h4>";
content += "<h4>Cell min: " + String(system_cell_min_voltage_mV) + " mV</h4>";
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
if (bms_status == 3) {
if (system_bms_status == ACTIVE) {
content += "<h4>BMS Status: OK </h4>";
} else if (system_bms_status == UPDATING) {
content += "<h4>BMS Status: UPDATING </h4>";
} else {
content += "<h4>BMS Status: FAULT </h4>";
}
if (bms_char_dis_status == 2) {
content += "<h4>Battery charging!</h4>";
} else if (bms_char_dis_status == 1) {
content += "<h4>Battery discharging!</h4>";
} else { //0 idle
if (system_battery_current_dA == 0) {
content += "<h4>Battery idle</h4>";
} else if (system_battery_current_dA < 0) {
content += "<h4>Battery discharging!</h4>";
} else { // > 0
content += "<h4>Battery charging!</h4>";
}
content += "<h4>Automatic contactor closing allowed:</h4>";
content += "<h4>Battery: ";
@ -630,19 +640,23 @@ String processor(const String& var) {
void onOTAStart() {
// Log when OTA has started
Serial.println("OTA update started!");
ESP32Can.CANStop();
bms_status = UPDATING; //Inform inverter that we are updating
LEDcolor = BLUE;
set_event(EVENT_OTA_UPDATE, 0);
// If already set, make a new attempt
clear_event(EVENT_OTA_UPDATE_TIMEOUT);
ota_active = true;
ota_timeout_timer.reset();
}
void onOTAProgress(size_t current, size_t final) {
bms_status = UPDATING; //Inform inverter that we are updating
LEDcolor = BLUE;
// Log every 1 second
if (millis() - ota_progress_millis > 1000) {
ota_progress_millis = millis();
Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
// Reset the "watchdog"
ota_timeout_timer.reset();
}
}
@ -652,16 +666,19 @@ void onOTAEnd(bool success) {
Serial.println("OTA update finished successfully!");
} else {
Serial.println("There was an error during OTA update!");
// If we fail without a timeout, try to restore CAN
ESP32Can.CANInit();
}
bms_status = UPDATING; //Inform inverter that we are updating
LEDcolor = BLUE;
ota_active = false;
clear_event(EVENT_OTA_UPDATE);
}
template <typename T> // This function makes power values appear as W when under 1000, and kW when over
String formatPowerValue(String label, T value, String unit, int precision) {
String result = "<h4 style='color: white;'>" + label + ": ";
if (std::is_same<T, float>::value || std::is_same<T, uint16_t>::value) {
if (std::is_same<T, float>::value || std::is_same<T, uint16_t>::value || std::is_same<T, uint32_t>::value) {
float convertedValue = static_cast<float>(value);
if (convertedValue >= 1000.0 || convertedValue <= -1000.0) {

View file

@ -16,26 +16,29 @@
#include "../mqtt/mqtt.h"
#endif
extern const char* version_number; // The current software version, shown on webserver
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern const char* version_number; // The current software version, shown on webserver
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000 , Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern const char* ssid;
extern const char* password;

View file

@ -106,28 +106,26 @@ CAN_frame_t BYD_210 = {.FIR = {.B =
.MsgID = 0x210,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static int discharge_current = 0;
static int charge_current = 0;
static int initialDataSent = 0;
static uint16_t discharge_current = 0;
static uint16_t charge_current = 0;
static int16_t temperature_average = 0;
static int16_t temp_min = 0;
static int16_t temp_max = 0;
static int inverter_voltage = 0;
static int inverter_SOC = 0;
static uint16_t inverter_voltage = 0;
static uint16_t inverter_SOC = 0;
static long inverter_timestamp = 0;
static bool initialDataSent = 0;
void update_values_can_byd() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//Calculate values
charge_current =
((max_target_charge_power * 10) / max_voltage); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
charge_current = ((system_max_charge_power_W * 10) /
system_max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
//The above calculation results in (30 000*10)/3700=81A
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
if (charge_current > MAXCHARGEAMP) {
charge_current = MAXCHARGEAMP; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
discharge_current = ((max_target_discharge_power * 10) /
max_voltage); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
discharge_current = ((system_max_discharge_power_W * 10) /
system_max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
//The above calculation results in (30 000*10)/3700=81A
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
if (discharge_current > MAXDISCHARGEAMP) {
@ -135,17 +133,15 @@ void update_values_can_byd() { //This function maps all the values fetched from
MAXDISCHARGEAMP; //Cap the value to the max allowed Amp. Some inverters cannot handle large values.
}
temp_min = temperature_min; //Convert from unsigned to signed
temp_max = temperature_max;
temperature_average = ((temp_max + temp_min) / 2);
temperature_average = ((system_temperature_max_dC + system_temperature_min_dC) / 2);
//Map values to CAN messages
//Maxvoltage (eg 400.0V = 4000 , 16bits long)
BYD_110.data.u8[0] = (max_voltage >> 8);
BYD_110.data.u8[1] = (max_voltage & 0x00FF);
BYD_110.data.u8[0] = (system_max_design_voltage_dV >> 8);
BYD_110.data.u8[1] = (system_max_design_voltage_dV & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long)
BYD_110.data.u8[2] = (min_voltage >> 8);
BYD_110.data.u8[3] = (min_voltage & 0x00FF);
BYD_110.data.u8[2] = (system_min_design_voltage_dV >> 8);
BYD_110.data.u8[3] = (system_min_design_voltage_dV & 0x00FF);
//Maximum discharge power allowed (Unit: A+1)
BYD_110.data.u8[4] = (discharge_current >> 8);
BYD_110.data.u8[5] = (discharge_current & 0x00FF);
@ -154,11 +150,11 @@ void update_values_can_byd() { //This function maps all the values fetched from
BYD_110.data.u8[7] = (charge_current & 0x00FF);
//SOC (100.00%)
BYD_150.data.u8[0] = (SOC >> 8);
BYD_150.data.u8[1] = (SOC & 0x00FF);
BYD_150.data.u8[0] = (system_scaled_SOC_pptt >> 8);
BYD_150.data.u8[1] = (system_scaled_SOC_pptt & 0x00FF);
//StateOfHealth (100.00%)
BYD_150.data.u8[2] = (StateOfHealth >> 8);
BYD_150.data.u8[3] = (StateOfHealth & 0x00FF);
BYD_150.data.u8[2] = (system_SOH_pptt >> 8);
BYD_150.data.u8[3] = (system_SOH_pptt & 0x00FF);
//Maximum charge power allowed (Unit: A+1)
BYD_150.data.u8[4] = (charge_current >> 8);
BYD_150.data.u8[5] = (charge_current & 0x00FF);
@ -167,21 +163,21 @@ void update_values_can_byd() { //This function maps all the values fetched from
BYD_150.data.u8[7] = (discharge_current & 0x00FF);
//Voltage (ex 370.0)
BYD_1D0.data.u8[0] = (battery_voltage >> 8);
BYD_1D0.data.u8[1] = (battery_voltage & 0x00FF);
BYD_1D0.data.u8[0] = (system_battery_voltage_dV >> 8);
BYD_1D0.data.u8[1] = (system_battery_voltage_dV & 0x00FF);
//Current (ex 81.0A)
BYD_1D0.data.u8[2] = (battery_current >> 8);
BYD_1D0.data.u8[3] = (battery_current & 0x00FF);
BYD_1D0.data.u8[2] = (system_battery_current_dA >> 8);
BYD_1D0.data.u8[3] = (system_battery_current_dA & 0x00FF);
//Temperature average
BYD_1D0.data.u8[4] = (temperature_average >> 8);
BYD_1D0.data.u8[5] = (temperature_average & 0x00FF);
//Temperature max
BYD_210.data.u8[0] = (temperature_max >> 8);
BYD_210.data.u8[1] = (temperature_max & 0x00FF);
BYD_210.data.u8[0] = (system_temperature_max_dC >> 8);
BYD_210.data.u8[1] = (system_temperature_max_dC & 0x00FF);
//Temperature min
BYD_210.data.u8[2] = (temperature_min >> 8);
BYD_210.data.u8[3] = (temperature_min & 0x00FF);
BYD_210.data.u8[2] = (system_temperature_min_dC >> 8);
BYD_210.data.u8[3] = (system_temperature_min_dC & 0x00FF);
#ifdef DEBUG_VIA_USB
if (char1_151 != 0) {

View file

@ -5,24 +5,28 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint8_t bms_status;
extern uint8_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern uint16_t min_voltage;
extern uint16_t max_voltage;
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
// These parameters need to be mapped for the inverter
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, true/false
void update_values_can_byd();
void send_can_byd();

View file

@ -32,15 +32,19 @@ void handle_update_data_modbusp201_byd() {
static uint16_t system_data[13];
system_data[0] = 0; // Id.: p201 Value.: 0 Scaled value.: 0 Comment.: Always 0
system_data[1] = 0; // Id.: p202 Value.: 0 Scaled value.: 0 Comment.: Always 0
system_data[2] =
(capacity_Wh); // Id.: p203 Value.: 32000 Scaled value.: 32kWh Comment.: Capacity rated, maximum value is 60000 (60kWh)
if (system_capacity_Wh > 60000) {
system_data[2] = 60000;
} else {
system_data[2] =
(system_capacity_Wh); // Id.: p203 Value.: 32000 Scaled value.: 32kWh Comment.: Capacity rated, maximum value is 60000 (60kWh)
}
system_data[3] = MAX_POWER; // Id.: p204 Value.: 32000 Scaled value.: 32kWh Comment.: Nominal capacity
system_data[4] =
MAX_POWER; // Id.: p205 Value.: 14500 Scaled value.: 30,42kW Comment.: Max Charge/Discharge Power=10.24kW (lowest value of 204 and 205 will be enforced by Gen24)
system_data[5] =
(max_voltage); // Id.: p206 Value.: 3667 Scaled value.: 362,7VDC Comment.: Max Voltage, if higher charging is not possible (goes into forced discharge)
(system_max_design_voltage_dV); // Id.: p206 Value.: 3667 Scaled value.: 362,7VDC Comment.: Max Voltage, if higher charging is not possible (goes into forced discharge)
system_data[6] =
(min_voltage); // Id.: p207 Value.: 2776 Scaled value.: 277,6VDC Comment.: Min Voltage, if lower Gen24 disables battery
(system_min_design_voltage_dV); // Id.: p207 Value.: 2776 Scaled value.: 277,6VDC Comment.: Min Voltage, if lower Gen24 disables battery
system_data[7] =
53248; // Id.: p208 Value.: 53248 Scaled value.: 53248 Comment.: Always 53248 for this BYD, Peak Charge power?
system_data[8] = 10; // Id.: p209 Value.: 10 Scaled value.: 10 Comment.: Always 10
@ -56,42 +60,62 @@ void handle_update_data_modbusp201_byd() {
void handle_update_data_modbusp301_byd() {
// Store the data into the array
static uint16_t battery_data[24];
if (battery_current == 0) {
static uint8_t bms_char_dis_status = STANDBY;
if (system_battery_current_dA == 0) {
bms_char_dis_status = STANDBY;
} else if (battery_current > 32767) { //Negative value = Discharging
} else if (system_battery_current_dA < 0) { //Negative value = Discharging
bms_char_dis_status = DISCHARGING;
} else { //Positive value = Charging
bms_char_dis_status = CHARGING;
}
if (bms_status == ACTIVE) {
if (system_bms_status == ACTIVE) {
battery_data[8] =
battery_voltage; // Id.: p309 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage outer (0 if status !=3, maybe a contactor closes when active): 173.4V
system_battery_voltage_dV; // Id.: p309 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage outer (0 if status !=3, maybe a contactor closes when active): 173.4V
} else {
battery_data[8] = 0;
}
battery_data[0] =
bms_status; // Id.: p301 Value.: 3 Scaled value.: 3 Comment.: status(*): ACTIVE - [0..5]<>[STANDBY,INACTIVE,DARKSTART,ACTIVE,FAULT,UPDATING]
battery_data[1] = 0; // Id.: p302 Value.: 0 Scaled value.: 0 Comment.: always 0
system_bms_status; // Id.: p301 Value.: 3 Scaled value.: 3 Comment.: status(*): ACTIVE - [0..5]<>[STANDBY,INACTIVE,DARKSTART,ACTIVE,FAULT,UPDATING]
battery_data[1] = 0; // Id.: p302 Value.: 0 Scaled value.: 0 Comment.: always 0
battery_data[2] = 128 + bms_char_dis_status; // Id.: p303 Value.: 130 Scaled value.: 130 Comment.: mode(*): normal
battery_data[3] = SOC; // Id.: p304 Value.: 1700 Scaled value.: 50% Comment.: SOC: (50% would equal 5000)
battery_data[4] = capacity_Wh; // Id.: p305 Value.: 32000 Scaled value.: 32kWh Comment.: tot cap:
battery_data[5] =
remaining_capacity_Wh; // Id.: p306 Value.: 13260 Scaled value.: 13,26kWh Comment.: remaining cap: 7.68kWh
battery_data[6] =
max_target_discharge_power; // Id.: p307 Value.: 25604 Scaled value.: 25,604kW Comment.: max/target discharge power: 0W (0W > restricts to no discharge)
battery_data[7] =
max_target_charge_power; // Id.: p308 Value.: 25604 Scaled value.: 25,604kW Comment.: max/target charge power: 4.3kW (during charge), both 307&308 can be set (>0) at the same time
battery_data[3] =
system_scaled_SOC_pptt; // Id.: p304 Value.: 1700 Scaled value.: 50% Comment.: SOC: (50% would equal 5000)
if (system_capacity_Wh > 60000) {
battery_data[4] = 60000;
} else {
battery_data[4] = system_capacity_Wh; // Id.: p305 Value.: 32000 Scaled value.: 32kWh Comment.: tot cap:
}
if (system_remaining_capacity_Wh > 60000) {
battery_data[5] = 60000;
} else {
battery_data[5] =
system_remaining_capacity_Wh; // Id.: p306 Value.: 13260 Scaled value.: 13,26kWh Comment.: remaining cap: 7.68kWh
}
if (system_max_discharge_power_W > 30000) {
battery_data[6] = 30000;
} else {
battery_data[6] =
system_max_discharge_power_W; // Id.: p307 Value.: 25604 Scaled value.: 25,604kW Comment.: max/target discharge power: 0W (0W > restricts to no discharge)
}
if (system_max_charge_power_W > 30000) {
battery_data[7] = 30000;
} else {
battery_data[7] =
system_max_charge_power_W; // Id.: p308 Value.: 25604 Scaled value.: 25,604kW Comment.: max/target charge power: 4.3kW (during charge), both 307&308 can be set (>0) at the same time
}
//Battery_data[8] set previously in function // Id.: p309 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage outer (0 if status !=3, maybe a contactor closes when active): 173.4V
battery_data[9] =
2000; // Id.: p310 Value.: 64121 Scaled value.: 6412,1W Comment.: Current Power to API: if>32768... -(65535-61760)=3775W
battery_data[10] =
battery_voltage; // Id.: p311 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage inner: 173.2V (LEAF voltage is in whole volts, need to add a decimal)
battery_data[11] = 2000; // Id.: p312 Value.: 64121 Scaled value.: 6412,1W Comment.: p310
system_battery_voltage_dV; // Id.: p311 Value.: 3161 Scaled value.: 316,1VDC Comment.: Batt Voltage inner: 173.2V
battery_data[11] = 2000; // Id.: p312 Value.: 64121 Scaled value.: 6412,1W Comment.: p310
battery_data[12] =
temperature_min; // Id.: p313 Value.: 75 Scaled value.: 7,5 Comment.: temp min: 7 degrees (if below 0....65535-t)
system_temperature_min_dC; // Id.: p313 Value.: 75 Scaled value.: 7,5 Comment.: temp min: 7 degrees (if below 0....65535-t)
battery_data[13] =
temperature_max; // Id.: p314 Value.: 95 Scaled value.: 9,5 Comment.: temp max: 9 degrees (if below 0....65535-t)
system_temperature_max_dC; // Id.: p314 Value.: 95 Scaled value.: 9,5 Comment.: temp max: 9 degrees (if below 0....65535-t)
battery_data[14] = 0; // Id.: p315 Value.: 0 Scaled value.: 0 Comment.: always 0
battery_data[15] = 0; // Id.: p316 Value.: 0 Scaled value.: 0 Comment.: always 0
battery_data[16] = 16; // Id.: p317 Value.: 0 Scaled value.: 0 Comment.: counter charge hi
@ -102,28 +126,28 @@ void handle_update_data_modbusp301_byd() {
battery_data[20] = 13; // Id.: p321 Value.: 0 Scaled value.: 0 Comment.: counter discharge hi
battery_data[21] =
52064; // Id.: p322 Value.: 0 Scaled value.: 0 Comment.: counter discharge lo....65536*92+7448 = 6036760 Wh?
battery_data[22] = 230; // Id.: p323 Value.: 0 Scaled value.: 0 Comment.: device temperature (23 degrees)
battery_data[23] = StateOfHealth; // Id.: p324 Value.: 9900 Scaled value.: 99% Comment.: SOH
battery_data[22] = 230; // Id.: p323 Value.: 0 Scaled value.: 0 Comment.: device temperature (23 degrees)
battery_data[23] = system_SOH_pptt; // Id.: p324 Value.: 9900 Scaled value.: 99% Comment.: SOH
static uint16_t i = 300;
memcpy(&mbPV[i], battery_data, sizeof(battery_data));
}
void verify_temperature_modbus() {
if (LFP_Chemistry) {
if (system_LFP_Chemistry) {
return; // Skip the following section
}
// This section checks if the battery temperature is negative, and incase it falls between -9.0 and -20.0C degrees
// The Fronius Gen24 (and other Fronius inverters also affected), will stop charge/discharge if the battery gets colder than -10°C.
// This is due to the original battery pack (BYD HVM), is a lithium iron phosphate battery, that cannot be charged in cold weather.
// When using EV packs with NCM/LMO/NCA chemsitry, this is not a problem, since these chemistries are OK for outdoor cold use.
if (temperature_min > 32768) { // Signed value on negative side
if (temperature_min < 65445 && temperature_min > 65335) { // Between -9.0 and -20.0C degrees
temperature_min = 65445; //Cap value to -9.0C
if (system_temperature_min_dC < 0) {
if (system_temperature_min_dC < -90 && system_temperature_min_dC > -200) { // Between -9.0 and -20.0C degrees
system_temperature_min_dC = -90; //Cap value to -9.0C
}
}
if (temperature_max > 32768) { // Signed value on negative side
if (temperature_max < 65445 && temperature_max > 65335) { // Between -9.0 and -20.0C degrees
temperature_max = 65445; //Cap value to -9.0C
if (system_temperature_max_dC < 0) { // Signed value on negative side
if (system_temperature_max_dC < -90 && system_temperature_max_dC > -200) { // Between -9.0 and -20.0C degrees
system_temperature_max_dC = -90; //Cap value to -9.0C
}
}
}

View file

@ -7,25 +7,24 @@
#define MAX_POWER 40960 //BYD Modbus specific value
extern uint16_t mbPV[MB_RTU_NUM_VALUES];
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint8_t bms_status;
extern uint8_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t max_power;
extern uint16_t max_voltage;
extern uint16_t min_voltage;
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool LFP_Chemistry;
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, true/false
extern bool system_LFP_Chemistry; //Bool, true/false
void handle_static_data_modbus_byd();
void verify_temperature_modbus();

View file

@ -21,6 +21,10 @@
#include "SMA-CAN.h"
#endif
#ifdef SMA_TRIPOWER_CAN
#include "SMA-TRIPOWER-CAN.h"
#endif
#ifdef SOFAR_CAN
#include "SOFAR-CAN.h"
#endif

View file

@ -10,14 +10,14 @@ void handle_update_data_modbus32051() {
// Store the data into the array
static uint16_t system_data[9];
system_data[0] = 1;
system_data[1] = 534; //Goes between 534-498 depending on SOC
system_data[2] = 110; //Goes between 110- -107 [NOTE, SIGNED VALUE]
system_data[3] = 0; //Goes between 0 and -1 [NOTE, SIGNED VALUE]
system_data[4] = 616; //Goes between 616- -520 [NOTE, SIGNED VALUE]
system_data[5] = temperature_max; //Temperature max?
system_data[6] = temperature_min; //Temperature min?
system_data[7] = (SOC / 100); //SOC 0-100%, no decimals
system_data[8] = 98; //Always 98 in logs
system_data[1] = 534; //Goes between 534-498 depending on SOC
system_data[2] = 110; //Goes between 110- -107 [NOTE, SIGNED VALUE]
system_data[3] = 0; //Goes between 0 and -1 [NOTE, SIGNED VALUE]
system_data[4] = 616; //Goes between 616- -520 [NOTE, SIGNED VALUE]
system_data[5] = system_temperature_max_dC; //Temperature max?
system_data[6] = system_temperature_min_dC; //Temperature min?
system_data[7] = (system_scaled_SOC_pptt / 100); //SOC 0-100%, no decimals
system_data[8] = 98; //Always 98 in logs
static uint16_t i = 2051;
memcpy(&mbPV[i], system_data, sizeof(system_data));
}
@ -26,18 +26,18 @@ void handle_update_data_modbus39500() {
// Store the data into the array
static uint16_t system_data[26];
system_data[0] = 0;
system_data[1] = capacity_Wh; //Capacity? 5000 with 5kWh battery
system_data[1] = system_capacity_Wh; //Capacity? 5000 with 5kWh battery
system_data[2] = 0;
system_data[3] = capacity_Wh; //Capacity? 5000 with 5kWh battery
system_data[3] = system_capacity_Wh; //Capacity? 5000 with 5kWh battery
system_data[4] = 0;
system_data[5] = 2500; //???
system_data[6] = 0;
system_data[7] = 2500; //???
system_data[8] = (SOC / 100); //SOC 0-100%, no decimals
system_data[7] = 2500; //???
system_data[8] = (system_scaled_SOC_pptt / 100); //SOC 0-100%, no decimals
system_data[9] =
1; //Running status, equiv to register 37762, 0 = Offline, 1 = Standby,2 = Running, 3 = Fault, 4 = sleep mode
system_data[10] = battery_voltage; //Battery bus voltage (766.5V = 7665)
system_data[11] = 9; //TODO: GOES LOWER WITH LOW SOC
system_data[10] = system_battery_voltage_dV; //Battery bus voltage (766.5V = 7665)
system_data[11] = 9; //TODO: GOES LOWER WITH LOW SOC
system_data[12] = 0;
system_data[13] = 699; //TODO: GOES LOWER WITH LOW SOC
system_data[14] = 1; //Always 1 in logs
@ -50,7 +50,7 @@ void handle_update_data_modbus39500() {
system_data[21] = 0;
system_data[22] = 0;
system_data[23] = 0;
system_data[24] = (SOC / 10); //SOC 0-100.0%, 1x decimal
system_data[24] = (system_scaled_SOC_pptt / 10); //SOC 0-100.0%, 1x decimal
system_data[25] = 0;
system_data[26] = 1; //Always 1 in logs
static uint16_t i = 9500;

View file

@ -6,24 +6,27 @@
#define MB_RTU_NUM_VALUES 30000
extern uint16_t mbPV[MB_RTU_NUM_VALUES];
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint8_t bms_status;
extern uint8_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t max_power;
extern uint16_t max_voltage;
extern uint16_t min_voltage;
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, true/false
void update_modbus_registers_luna2000();
void handle_update_data_modbus32051();

View file

@ -182,53 +182,53 @@ void update_values_can_pylon() { //This function maps all the values fetched fr
PYLON_4281.data.u8[3] = 0;
//Voltage (370.0)
PYLON_4210.data.u8[0] = (battery_voltage >> 8);
PYLON_4210.data.u8[1] = (battery_voltage & 0x00FF);
PYLON_4211.data.u8[0] = (battery_voltage >> 8);
PYLON_4211.data.u8[1] = (battery_voltage & 0x00FF);
PYLON_4210.data.u8[0] = (system_battery_voltage_dV >> 8);
PYLON_4210.data.u8[1] = (system_battery_voltage_dV & 0x00FF);
PYLON_4211.data.u8[0] = (system_battery_voltage_dV >> 8);
PYLON_4211.data.u8[1] = (system_battery_voltage_dV & 0x00FF);
//Current (TODO: SIGNED? Or looks like it could be just offset, in that case the below line wont work)
PYLON_4210.data.u8[2] = (battery_current >> 8);
PYLON_4210.data.u8[3] = (battery_current & 0x00FF);
PYLON_4211.data.u8[2] = (battery_current >> 8);
PYLON_4211.data.u8[3] = (battery_current & 0x00FF);
//Current (15.0)
PYLON_4210.data.u8[2] = (system_battery_current_dA >> 8);
PYLON_4210.data.u8[3] = (system_battery_current_dA & 0x00FF);
PYLON_4211.data.u8[2] = (system_battery_current_dA >> 8);
PYLON_4211.data.u8[3] = (system_battery_current_dA & 0x00FF);
//SOC (100.00%)
PYLON_4210.data.u8[6] = (SOC * 0.01); //Remove decimals
PYLON_4211.data.u8[6] = (SOC * 0.01); //Remove decimals
PYLON_4210.data.u8[6] = (system_scaled_SOC_pptt * 0.01); //Remove decimals
PYLON_4211.data.u8[6] = (system_scaled_SOC_pptt * 0.01); //Remove decimals
//StateOfHealth (100.00%)
PYLON_4210.data.u8[7] = (StateOfHealth * 0.01);
PYLON_4211.data.u8[7] = (StateOfHealth * 0.01);
PYLON_4210.data.u8[7] = (system_SOH_pptt * 0.01);
PYLON_4211.data.u8[7] = (system_SOH_pptt * 0.01);
#ifdef INVERT_VOLTAGE //Useful for Sofar inverters \
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage
PYLON_4220.data.u8[0] = (max_voltage & 0x00FF);
PYLON_4220.data.u8[1] = (max_voltage >> 8);
PYLON_4221.data.u8[0] = (max_voltage & 0x00FF);
PYLON_4221.data.u8[1] = (max_voltage >> 8);
PYLON_4220.data.u8[0] = (system_max_design_voltage_dV & 0x00FF);
PYLON_4220.data.u8[1] = (system_max_design_voltage_dV >> 8);
PYLON_4221.data.u8[0] = (system_max_design_voltage_dV & 0x00FF);
PYLON_4221.data.u8[1] = (system_max_design_voltage_dV >> 8);
//Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage
PYLON_4220.data.u8[2] = (min_voltage & 0x00FF);
PYLON_4220.data.u8[3] = (min_voltage >> 8);
PYLON_4221.data.u8[2] = (min_voltage & 0x00FF);
PYLON_4221.data.u8[3] = (min_voltage >> 8);
PYLON_4220.data.u8[2] = (system_min_design_voltage_dV & 0x00FF);
PYLON_4220.data.u8[3] = (system_min_design_voltage_dV >> 8);
PYLON_4221.data.u8[2] = (system_min_design_voltage_dV & 0x00FF);
PYLON_4221.data.u8[3] = (system_min_design_voltage_dV >> 8);
#else
//Minvoltage (eg 300.0V = 3000 , 16bits long) Charge Cutoff Voltage
PYLON_4220.data.u8[0] = (min_voltage >> 8);
PYLON_4220.data.u8[1] = (min_voltage & 0x00FF);
PYLON_4221.data.u8[0] = (min_voltage >> 8);
PYLON_4221.data.u8[1] = (min_voltage & 0x00FF);
PYLON_4220.data.u8[0] = (system_min_design_voltage_dV >> 8);
PYLON_4220.data.u8[1] = (system_min_design_voltage_dV & 0x00FF);
PYLON_4221.data.u8[0] = (system_min_design_voltage_dV >> 8);
PYLON_4221.data.u8[1] = (system_min_design_voltage_dV & 0x00FF);
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Discharge Cutoff Voltage
PYLON_4220.data.u8[2] = (max_voltage >> 8);
PYLON_4220.data.u8[3] = (max_voltage & 0x00FF);
PYLON_4221.data.u8[2] = (max_voltage >> 8);
PYLON_4221.data.u8[3] = (max_voltage & 0x00FF);
PYLON_4220.data.u8[2] = (system_max_design_voltage_dV >> 8);
PYLON_4220.data.u8[3] = (system_max_design_voltage_dV & 0x00FF);
PYLON_4221.data.u8[2] = (system_max_design_voltage_dV >> 8);
PYLON_4221.data.u8[3] = (system_max_design_voltage_dV & 0x00FF);
#endif
//In case we run into any errors/faults, we can set charge / discharge forbidden
if (bms_status == FAULT) {
if (system_bms_status == FAULT) {
PYLON_4280.data.u8[0] = 0xAA;
PYLON_4280.data.u8[1] = 0xAA;
PYLON_4280.data.u8[2] = 0xAA;

View file

@ -5,24 +5,27 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint8_t bms_status;
extern uint8_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern uint16_t min_voltage;
extern uint16_t max_voltage;
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, true/false
void update_values_can_pylon();
void receive_can_pylon(CAN_frame_t rx_frame);

View file

@ -1,6 +1,7 @@
//SERIAL-LINK-TRANSMITTER-INVERTER.cpp
#include "SERIAL-LINK-TRANSMITTER-INVERTER.h"
#include "../devboard/utils/events.h"
/*
* SerialDataLink
@ -8,7 +9,7 @@
* Will transmit max 16 int variable - receive none
*/
#define BATTERY_SEND_NUM_VARIABLES 17
#define BATTERY_SEND_NUM_VARIABLES 16
#define BATTERY_RECV_NUM_VARIABLES 1
#ifdef BATTERY_RECV_NUM_VARIABLES
@ -107,10 +108,9 @@ void manageSerialLinkTransmitter() {
Serial.println("SerialDataLink : max_target_discharge_power = 0");
Serial.println("SerialDataLink : max_target_charge_power = 0");
bms_status = 4; //FAULT
max_target_discharge_power = 0;
max_target_charge_power = 0;
LEDcolor = RED;
system_max_discharge_power_W = 0;
system_max_charge_power_W = 0;
set_event(EVENT_SERIAL_TX_FAILURE, 0);
// throw error
}
/*
@ -129,23 +129,22 @@ void manageSerialLinkTransmitter() {
if (currentTime - updateDataTime > 999) {
updateDataTime = currentTime;
dataLinkTransmit.updateData(0, SOC);
dataLinkTransmit.updateData(1, StateOfHealth);
dataLinkTransmit.updateData(2, battery_voltage);
dataLinkTransmit.updateData(3, battery_current);
dataLinkTransmit.updateData(4, capacity_Wh);
dataLinkTransmit.updateData(5, remaining_capacity_Wh);
dataLinkTransmit.updateData(6, max_target_discharge_power);
dataLinkTransmit.updateData(7, max_target_charge_power);
dataLinkTransmit.updateData(8, bms_status);
dataLinkTransmit.updateData(9, bms_char_dis_status);
dataLinkTransmit.updateData(10, stat_batt_power);
dataLinkTransmit.updateData(11, temperature_min);
dataLinkTransmit.updateData(12, temperature_max);
dataLinkTransmit.updateData(13, cell_max_voltage);
dataLinkTransmit.updateData(14, cell_min_voltage);
dataLinkTransmit.updateData(15, (int16_t)LFP_Chemistry);
dataLinkTransmit.updateData(16, batteryAllowsContactorClosing);
dataLinkTransmit.updateData(0, system_real_SOC_pptt);
dataLinkTransmit.updateData(1, system_SOH_pptt);
dataLinkTransmit.updateData(2, system_battery_voltage_dV);
dataLinkTransmit.updateData(3, system_battery_current_dA);
dataLinkTransmit.updateData(4, system_capacity_Wh);
dataLinkTransmit.updateData(5, system_remaining_capacity_Wh);
dataLinkTransmit.updateData(6, system_max_discharge_power_W);
dataLinkTransmit.updateData(7, system_max_charge_power_W);
dataLinkTransmit.updateData(8, system_bms_status);
dataLinkTransmit.updateData(9, system_active_power_W);
dataLinkTransmit.updateData(10, system_temperature_min_dC);
dataLinkTransmit.updateData(11, system_temperature_max_dC);
dataLinkTransmit.updateData(12, system_cell_max_voltage_mV);
dataLinkTransmit.updateData(13, system_cell_min_voltage_mV);
dataLinkTransmit.updateData(14, (int16_t)system_LFP_Chemistry);
dataLinkTransmit.updateData(15, batteryAllowsContactorClosing);
}
}
}
@ -153,37 +152,35 @@ void manageSerialLinkTransmitter() {
void printSendingValues() {
Serial.println("Values from battery: ");
Serial.print("SOC: ");
Serial.print(SOC);
Serial.print(system_real_SOC_pptt);
Serial.print(" SOH: ");
Serial.print(StateOfHealth);
Serial.print(system_SOH_pptt);
Serial.print(" Voltage: ");
Serial.print(battery_voltage);
Serial.print(system_battery_voltage_dV);
Serial.print(" Current: ");
Serial.print(battery_current);
Serial.print(system_battery_current_dA);
Serial.print(" Capacity: ");
Serial.print(capacity_Wh);
Serial.print(system_capacity_Wh);
Serial.print(" Remain cap: ");
Serial.print(remaining_capacity_Wh);
Serial.print(system_remaining_capacity_Wh);
Serial.print(" Max discharge W: ");
Serial.print(max_target_discharge_power);
Serial.print(system_max_discharge_power_W);
Serial.print(" Max charge W: ");
Serial.print(max_target_charge_power);
Serial.print(system_max_charge_power_W);
Serial.print(" BMS status: ");
Serial.print(bms_status);
Serial.print(" BMS status dis/cha: ");
Serial.print(bms_char_dis_status);
Serial.print(system_bms_status);
Serial.print(" Power: ");
Serial.print(stat_batt_power);
Serial.print(system_active_power_W);
Serial.print(" Temp min: ");
Serial.print(temperature_min);
Serial.print(system_temperature_min_dC);
Serial.print(" Temp max: ");
Serial.print(temperature_max);
Serial.print(system_temperature_max_dC);
Serial.print(" Cell max: ");
Serial.print(cell_max_voltage);
Serial.print(system_cell_max_voltage_mV);
Serial.print(" Cell min: ");
Serial.print(cell_min_voltage);
Serial.print(system_cell_min_voltage_mV);
Serial.print(" LFP : ");
Serial.print(LFP_Chemistry);
Serial.print(system_LFP_Chemistry);
Serial.print(" batteryAllowsContactorClosing: ");
Serial.print(batteryAllowsContactorClosing);
Serial.print(" inverterAllowsContactorClosing: ");

View file

@ -1,5 +1,3 @@
//SERIAL-LINK-TRANSMITTER-INVERTER.h
#ifndef SERIAL_LINK_TRANSMITTER_INVERTER_H
#define SERIAL_LINK_TRANSMITTER_INVERTER_H
@ -9,29 +7,25 @@
#include "../lib/mackelec-SerialDataLink/SerialDataLink.h"
// These parameters need to be mapped for the inverter
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool LFP_Chemistry;
extern uint16_t CANerror;
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint8_t system_bms_status; //Enum 0-5
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool system_LFP_Chemistry; //Bool, true/false
// parameters received from receiver
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, true/false
void manageSerialLinkTransmitter();

View file

@ -2,33 +2,11 @@
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
//TODO: change CAN sending routine once confirmed that 500ms interval is OK for this battery type
/* TODO: Map error bits in 0x158 */
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis1s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis2s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis3s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis4s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis5s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis6s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis7s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis8s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis9s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis10s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis11s = 0; // will store last time a Xs CAN Message was send
static unsigned long previousMillis12s = 0; // will store last time a Xs CAN Message was send
static const int interval1s = 100; // interval (ms) at which send CAN Messages
static const int interval2s = 102; // interval (ms) at which send CAN Messages
static const int interval3s = 104; // interval (ms) at which send CAN Messages
static const int interval4s = 106; // interval (ms) at which send CAN Messages
static const int interval5s = 108; // interval (ms) at which send CAN Messages
static const int interval6s = 110; // interval (ms) at which send CAN Messages
static const int interval7s = 112; // interval (ms) at which send CAN Messages
static const int interval8s = 114; // interval (ms) at which send CAN Messages
static const int interval9s = 116; // interval (ms) at which send CAN Messages
static const int interval10s = 118; // interval (ms) at which send CAN Messages
static const int interval11s = 120; // interval (ms) at which send CAN Messages
static const int interval12s = 122; // interval (ms) at which send CAN Messages
static unsigned long previousMillis100ms = 0; // will store last time a 100ms CAN Message was send
static const int interval100ms = 100; // interval (ms) at which send CAN Messages
//Actual content messages
static const CAN_frame_t SMA_558 = {
@ -118,35 +96,36 @@ CAN_frame_t SMA_158 = {.FIR = {.B =
.MsgID = 0x158,
.data = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x6A, 0xAA, 0xAA}};
static int discharge_current = 0;
static int charge_current = 0;
static int temperature_average = 0;
static int ampere_hours_remaining = 0;
static int16_t discharge_current = 0;
static int16_t charge_current = 0;
static int16_t temperature_average = 0;
static uint16_t ampere_hours_remaining = 0;
void update_values_can_sma() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//Calculate values
charge_current =
((max_target_charge_power * 10) / max_voltage); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
charge_current = ((system_max_charge_power_W * 10) /
system_max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
//The above calculation results in (30 000*10)/3700=81A
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
discharge_current = ((max_target_discharge_power * 10) /
max_voltage); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
discharge_current = ((system_max_discharge_power_W * 10) /
system_max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
//The above calculation results in (30 000*10)/3700=81A
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
temperature_average = ((temperature_max + temperature_min) / 2);
temperature_average = ((system_temperature_max_dC + system_temperature_min_dC) / 2);
ampere_hours_remaining =
((remaining_capacity_Wh / battery_voltage) * 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
((system_remaining_capacity_Wh / system_battery_voltage_dV) * 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
//Map values to CAN messages
//Maxvoltage (eg 400.0V = 4000 , 16bits long)
SMA_358.data.u8[0] = (max_voltage >> 8);
SMA_358.data.u8[1] = (max_voltage & 0x00FF);
SMA_358.data.u8[0] = (system_max_design_voltage_dV >> 8);
SMA_358.data.u8[1] = (system_max_design_voltage_dV & 0x00FF);
//Minvoltage (eg 300.0V = 3000 , 16bits long)
SMA_358.data.u8[2] = (min_voltage >> 8); //Minvoltage behaves strange on SMA, cuts out at 56% of the set value?
SMA_358.data.u8[3] = (min_voltage & 0x00FF);
SMA_358.data.u8[2] =
(system_min_design_voltage_dV >> 8); //Minvoltage behaves strange on SMA, cuts out at 56% of the set value?
SMA_358.data.u8[3] = (system_min_design_voltage_dV & 0x00FF);
//Discharge limited current, 500 = 50A, (0.1, A)
SMA_358.data.u8[4] = (discharge_current >> 8);
SMA_358.data.u8[5] = (discharge_current & 0x00FF);
@ -155,37 +134,97 @@ void update_values_can_sma() { //This function maps all the values fetched from
SMA_358.data.u8[7] = (charge_current & 0x00FF);
//SOC (100.00%)
SMA_3D8.data.u8[0] = (SOC >> 8);
SMA_3D8.data.u8[1] = (SOC & 0x00FF);
SMA_3D8.data.u8[0] = (system_scaled_SOC_pptt >> 8);
SMA_3D8.data.u8[1] = (system_scaled_SOC_pptt & 0x00FF);
//StateOfHealth (100.00%)
SMA_3D8.data.u8[2] = (StateOfHealth >> 8);
SMA_3D8.data.u8[3] = (StateOfHealth & 0x00FF);
SMA_3D8.data.u8[2] = (system_SOH_pptt >> 8);
SMA_3D8.data.u8[3] = (system_SOH_pptt & 0x00FF);
//State of charge (AH, 0.1)
SMA_3D8.data.u8[4] = (ampere_hours_remaining >> 8);
SMA_3D8.data.u8[5] = (ampere_hours_remaining & 0x00FF);
//Voltage (370.0)
SMA_4D8.data.u8[0] = (battery_voltage >> 8);
SMA_4D8.data.u8[1] = (battery_voltage & 0x00FF);
SMA_4D8.data.u8[0] = (system_battery_voltage_dV >> 8);
SMA_4D8.data.u8[1] = (system_battery_voltage_dV & 0x00FF);
//Current (TODO: signed OK?)
SMA_4D8.data.u8[2] = (battery_current >> 8);
SMA_4D8.data.u8[3] = (battery_current & 0x00FF);
SMA_4D8.data.u8[2] = (system_battery_current_dA >> 8);
SMA_4D8.data.u8[3] = (system_battery_current_dA & 0x00FF);
//Temperature average
SMA_4D8.data.u8[4] = (temperature_average >> 8);
SMA_4D8.data.u8[5] = (temperature_average & 0x00FF);
//Battery ready
if (system_bms_status == ACTIVE) {
SMA_4D8.data.u8[6] = READY_STATE;
} else {
SMA_4D8.data.u8[6] = STOP_STATE;
}
//Error bits
/*
//SMA_158.data.u8[0] = //bit12 Fault high temperature, bit34Battery cellundervoltage, bit56 Battery cell overvoltage, bit78 batterysystemdefect
//TODO: add all error bits
//TODO: add all error bits. Sending message with all 0xAA until that.
0x158 can be used to send error messages or warnings.
Each message is defined of two bits:
01=message triggered
10=no message triggered
0xA9=10101001, triggers first message
0xA6=10100110, triggers second message
0x9A=10011010, triggers third message
0x6A=01101010, triggers forth message
bX defines the byte
b0 A9 Battery system defect
b0 A6 Battery cell overvoltage fault
b0 9A Battery cell undervoltage fault
b0 6A Battery high temperature fault
b1 A9 Battery low temperature fault
b1 A6 Battery high temperature fault
b1 9A Battery low temperature fault
b1 6A Overload (reboot required)
b2 A9 Overload (reboot required)
b2 A6 Incorrect switch position for the battery disconnection point
b2 9A Battery system short circuit
b2 6A Internal battery hardware fault
b3 A9 Battery imbalancing fault
b3 A6 Battery service life expiry
b3 9A Battery system thermal management defective
b3 6A Internal battery hardware fault
b4 A9 Battery system defect (warning)
b4 A6 Battery cell overvoltage fault (warning)
b4 9A Battery cell undervoltage fault (warning)
b4 6A Battery high temperature fault (warning)
b5 A9 Battery low temperature fault (warning)
b5 A6 Battery high temperature fault (warning)
b5 9A Battery low temperature fault (warning)
b5 6A Self-diagnosis (warning)
b6 A9 Self-diagnosis (warning)
b6 A6 Incorrect switch position for the battery disconnection point (warning)
b6 9A Battery system short circuit (warning)
b6 6A Internal battery hardware fault (warning)
b7 A9 Battery imbalancing fault (warning)
b7 A6 Battery service life expiry (warning)
b7 9A Battery system thermal management defective (warning)
b7 6A Internal battery hardware fault (warning)
*/
}
void receive_can_sma(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0x660: //Message originating from SMA inverter
case 0x360: //Message originating from SMA inverter - Voltage and current
//Frame0-1 Voltage
//Frame2-3 Current
break;
case 0x5E0: //Message originating from SMA inverter
case 0x420: //Message originating from SMA inverter - Timestamp
//Frame0-3 Timestamp
break;
case 0x560: //Message originating from SMA inverter
case 0x3E0: //Message originating from SMA inverter - ?
break;
case 0x5E0: //Message originating from SMA inverter - String
break;
case 0x560: //Message originating from SMA inverter - Init
break;
default:
break;
@ -195,65 +234,21 @@ void receive_can_sma(CAN_frame_t rx_frame) {
void send_can_sma() {
unsigned long currentMillis = millis();
// Send CAN Message every X ms, 1000 for testing
if (currentMillis - previousMillis1s >= interval1s) {
previousMillis1s = currentMillis;
// Send CAN Message every 100ms
if (currentMillis - previousMillis100ms >= interval100ms) {
previousMillis100ms = currentMillis;
ESP32Can.CANWriteFrame(&SMA_558);
}
if (currentMillis - previousMillis2s >= interval2s) {
previousMillis2s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_598);
}
if (currentMillis - previousMillis3s >= interval3s) {
previousMillis3s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_5D8);
}
if (currentMillis - previousMillis4s >= interval4s) {
previousMillis4s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_618_1);
}
if (currentMillis - previousMillis5s >= interval5s) {
previousMillis5s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_618_2);
}
if (currentMillis - previousMillis6s >= interval6s) {
previousMillis6s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_618_3);
}
if (currentMillis - previousMillis7s >= interval7s) {
previousMillis7s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_618_1); // TODO, should these 3x
ESP32Can.CANWriteFrame(&SMA_618_2); // be sent as batch?
ESP32Can.CANWriteFrame(&SMA_618_3); // or alternate on each send?
ESP32Can.CANWriteFrame(&SMA_358);
}
if (currentMillis - previousMillis8s >= interval8s) {
previousMillis8s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_3D8);
}
if (currentMillis - previousMillis9s >= interval9s) {
previousMillis9s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_458);
}
if (currentMillis - previousMillis10s >= interval10s) {
previousMillis10s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_518);
}
if (currentMillis - previousMillis11s >= interval11s) {
previousMillis11s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_4D8);
}
if (currentMillis - previousMillis12s >= interval12s) {
previousMillis12s = currentMillis;
ESP32Can.CANWriteFrame(&SMA_158);
}
}

View file

@ -5,26 +5,30 @@
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint16_t min_voltage;
extern uint16_t max_voltage;
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, true/false
#define READY_STATE 0x03
#define STOP_STATE 0x02
void update_values_can_sma();
void send_can_sma();

View file

@ -0,0 +1,365 @@
#include "SMA-TRIPOWER-CAN.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
/* TODO:
- Figure out the manufacturer info needed in send_tripower_init() CAN messages
- CAN logs from real system might be needed
- Figure out how cellvoltages need to be displayed
- Figure out if sending send_tripower_init() like we do now is OK
- Figure out how to send the non-cyclic messages when needed
*/
/* Do not change code below unless you are sure what you are doing */
static unsigned long previousMillis500ms = 0; // will store last time a 100ms CAN Message was send
static const int interval500ms = 100; // interval (ms) at which send CAN Messages
//Actual content messages
static CAN_frame_t SMA_00D = { // Battery Limits
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x00D,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_00F = { // Battery State
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x00F,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_011 = { // Battery Energy
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x011,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_013 = { // Battery Measurements
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x013,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_014 = { // Battery Tempeartures and Cellvoltages
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x014,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_005 = { // Battery Alarms 1
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x005,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_007 = { // Battery Alarms 2
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x007,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_006 = { // Battery Error Codes
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x006,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_008 = { // Battery Events
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x008,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_015 = { // Battery Data 1
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x015,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_016 = { // Battery Data 2
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x016,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_017 = { // Battery Manufacturer
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x017,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static CAN_frame_t SMA_018 = { // Battery Name
.FIR = {.B =
{
.DLC = 8,
.FF = CAN_frame_std,
}},
.MsgID = 0x018,
.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static int discharge_current = 0;
static int charge_current = 0;
static int temperature_average = 0;
static int ampere_hours_remaining = 0;
static int ampere_hours_max = 0;
static bool batteryAlarm = false;
static bool BMSevent = false;
enum BatteryState { NA, INIT, BAT_STANDBY, OPERATE, WARNING, FAULTED, UPDATE, BAT_UPDATE };
BatteryState batteryState = OPERATE;
enum InverterControlFlags {
EMG_CHARGE_REQUEST,
EMG_DISCHARGE_REQUEST,
NOT_ENOUGH_ENERGY_FOR_START,
INVERTER_STAY_ON,
FORCED_BATTERY_SHUTDOWN,
RESERVED,
BATTERY_UPDATE_AVAILABLE,
NO_BATTERY_UPDATED_BY_INV
};
InverterControlFlags inverterControlFlags = BATTERY_UPDATE_AVAILABLE;
enum Events0 {
START_SOC_CALIBRATE,
STOP_SOC_CALIBRATE,
START_POWERLIMIT,
STOP_POWERLIMIT,
PREVENTATIVE_BAT_SHUTDOWN,
THERMAL_MANAGEMENT,
START_BALANCING,
STOP_BALANCING
};
Events0 events0 = START_BALANCING;
enum Events1 { START_BATTERY_SELFTEST, STOP_BATTERY_SELFTEST };
Events1 events1 = START_BATTERY_SELFTEST;
enum Command2Battery { IDLE, RUN, NOT_USED1, NOT_USED2, SHUTDOWN, FIRMWARE_UPDATE, BATSELFUPDATE, NOT_USED3 };
Command2Battery command2Battery = RUN;
enum InvInitState { SYSTEM_FREQUENCY, XPHASE_SYSTEM, BLACKSTART_OPERATION };
InvInitState invInitState = SYSTEM_FREQUENCY;
void update_values_can_sma_tripower() { //This function maps all the values fetched from battery CAN to the inverter CAN
//Calculate values
charge_current = ((system_max_charge_power_W * 10) /
system_max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
//The above calculation results in (30 000*10)/3700=81A
charge_current = (charge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
discharge_current = ((system_max_discharge_power_W * 10) /
system_max_design_voltage_dV); //Charge power in W , max volt in V+1decimal (P=UI, solve for I)
//The above calculation results in (30 000*10)/3700=81A
discharge_current = (discharge_current * 10); //Value needs a decimal before getting sent to inverter (81.0A)
temperature_average = ((system_temperature_max_dC + system_temperature_min_dC) / 2);
ampere_hours_remaining =
((system_remaining_capacity_Wh / system_battery_voltage_dV) * 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
ampere_hours_max =
((system_capacity_Wh / system_battery_voltage_dV) * 100); //(WH[10000] * V+1[3600])*100 = 270 (27.0Ah)
batteryState = OPERATE;
inverterControlFlags = INVERTER_STAY_ON;
//Map values to CAN messages
// Battery Limits
//Battery Max Charge Voltage (eg 400.0V = 4000 , 16bits long)
SMA_00D.data.u8[0] = (system_max_design_voltage_dV >> 8);
SMA_00D.data.u8[1] = (system_max_design_voltage_dV & 0x00FF);
//Battery Min Discharge Voltage (eg 300.0V = 3000 , 16bits long)
SMA_00D.data.u8[2] = (system_min_design_voltage_dV >> 8);
SMA_00D.data.u8[3] = (system_min_design_voltage_dV & 0x00FF);
//Discharge limited current, 500 = 50A, (0.1, A)
SMA_00D.data.u8[4] = (discharge_current >> 8);
SMA_00D.data.u8[5] = (discharge_current & 0x00FF);
//Charge limited current, 125 =12.5A (0.1, A)
SMA_00D.data.u8[6] = (charge_current >> 8);
SMA_00D.data.u8[7] = (charge_current & 0x00FF);
// Battery State
//SOC (100.00%)
SMA_00F.data.u8[0] = (system_scaled_SOC_pptt >> 8);
SMA_00F.data.u8[1] = (system_scaled_SOC_pptt & 0x00FF);
//StateOfHealth (100.00%)
SMA_00F.data.u8[2] = (system_SOH_pptt >> 8);
SMA_00F.data.u8[3] = (system_SOH_pptt & 0x00FF);
//State of charge (AH, 0.1)
SMA_00F.data.u8[4] = (ampere_hours_remaining >> 8);
SMA_00F.data.u8[5] = (ampere_hours_remaining & 0x00FF);
//Fully charged (AH, 0.1)
SMA_00F.data.u8[6] = (ampere_hours_max >> 8);
SMA_00F.data.u8[7] = (ampere_hours_max & 0x00FF);
// Battery Energy
//Charged Energy Counter TODO: are these needed?
//SMA_011.data.u8[0] = (X >> 8);
//SMA_011.data.u8[1] = (X & 0x00FF);
//SMA_011.data.u8[2] = (X >> 8);
//SMA_011.data.u8[3] = (X & 0x00FF);
//Discharged Energy Counter TODO: are these needed?
//SMA_011.data.u8[4] = (X >> 8);
//SMA_011.data.u8[5] = (X & 0x00FF);
//SMA_011.data.u8[6] = (X >> 8);
//SMA_011.data.u8[7] = (X & 0x00FF);
// Battery Measurements
//Voltage (370.0)
SMA_013.data.u8[0] = (system_battery_voltage_dV >> 8);
SMA_013.data.u8[1] = (system_battery_voltage_dV & 0x00FF);
//Current (TODO: signed OK?)
SMA_013.data.u8[2] = (system_battery_current_dA >> 8);
SMA_013.data.u8[3] = (system_battery_current_dA & 0x00FF);
//Temperature average
SMA_013.data.u8[4] = (temperature_average >> 8);
SMA_013.data.u8[5] = (temperature_average & 0x00FF);
//Battery state
SMA_013.data.u8[6] = batteryState;
SMA_013.data.u8[6] = inverterControlFlags;
// Battery Temperature and Cellvoltages
// Battery max temperature
SMA_014.data.u8[0] = (system_temperature_max_dC >> 8);
SMA_014.data.u8[1] = (system_temperature_max_dC & 0x00FF);
// Battery min temperature
SMA_014.data.u8[2] = (system_temperature_min_dC >> 8);
SMA_014.data.u8[3] = (system_temperature_min_dC & 0x00FF);
// Battery Cell Voltage (sum)
//SMA_014.data.u8[4] = (??? >> 8); //TODO scaling?
//SMA_014.data.u8[5] = (??? & 0x00FF); //TODO scaling?
// Cell voltage min
//SMA_014.data.u8[6] = (??? >> 8); //TODO scaling? 0-255
// Cell voltage max
//SMA_014.data.u8[7] = (??? >> 8); //TODO scaling? 0-255
//SMA_006.data.u8[0] = (ErrorCode >> 8);
//SMA_006.data.u8[1] = (ErrorCode & 0x00FF);
//SMA_006.data.u8[2] = ModuleNumber;
//SMA_006.data.u8[3] = ErrorLevel;
//SMA_008.data.u8[0] = Events0;
//SMA_008.data.u8[1] = Events1;
//SMA_005.data.u8[0] = BMSalarms0;
//SMA_005.data.u8[1] = BMSalarms1;
//SMA_005.data.u8[2] = BMSalarms2;
//SMA_005.data.u8[3] = BMSalarms3;
//SMA_005.data.u8[4] = BMSalarms4;
//SMA_005.data.u8[5] = BMSalarms5;
//SMA_005.data.u8[6] = BMSalarms6;
//SMA_005.data.u8[7] = BMSalarms7;
//SMA_007.data.u8[0] = DCDCalarms0;
//SMA_007.data.u8[1] = DCDCalarms1;
//SMA_007.data.u8[2] = DCDCalarms2;
//SMA_007.data.u8[3] = DCDCalarms3;
//SMA_007.data.u8[4] = DCDCwarnings0;
//SMA_007.data.u8[5] = DCDCwarnings1;
//SMA_007.data.u8[6] = DCDCwarnings2;
//SMA_007.data.u8[7] = DCDCwarnings3;
//SMA_015.data.u8[0] = BatterySystemVersion;
//SMA_015.data.u8[1] = BatterySystemVersion;
//SMA_015.data.u8[2] = BatterySystemVersion;
//SMA_015.data.u8[3] = BatterySystemVersion;
//SMA_015.data.u8[4] = BatteryCapacity;
//SMA_015.data.u8[5] = BatteryCapacity;
//SMA_015.data.u8[6] = NumberOfModules;
//SMA_015.data.u8[7] = BatteryManufacturerID;
//SMA_016.data.u8[0] = SerialNumber;
//SMA_016.data.u8[1] = SerialNumber;
//SMA_016.data.u8[2] = SerialNumber;
//SMA_016.data.u8[3] = SerialNumber;
//SMA_016.data.u8[4] = ManufacturingDate;
//SMA_016.data.u8[5] = ManufacturingDate;
//SMA_016.data.u8[6] = ManufacturingDate;
//SMA_016.data.u8[7] = ManufacturingDate;
//SMA_017.data.u8[0] = Multiplex;
//SMA_017.data.u8[1] = ManufacturerName;
//SMA_017.data.u8[2] = ManufacturerName;
//SMA_017.data.u8[3] = ManufacturerName;
//SMA_017.data.u8[4] = ManufacturerName;
//SMA_017.data.u8[5] = ManufacturerName;
//SMA_017.data.u8[6] = ManufacturerName;
//SMA_017.data.u8[7] = ManufacturerName;
//SMA_018.data.u8[0] = Multiplex;
//SMA_018.data.u8[1] = BatteryName;
//SMA_018.data.u8[2] = BatteryName;
//SMA_018.data.u8[3] = BatteryName;
//SMA_018.data.u8[4] = BatteryName;
//SMA_018.data.u8[5] = BatteryName;
//SMA_018.data.u8[6] = BatteryName;
//SMA_018.data.u8[7] = BatteryName;
}
void receive_can_sma_tripower(CAN_frame_t rx_frame) {
switch (rx_frame.MsgID) {
case 0x00D: //Inverter Measurements
break;
case 0x00F: //Inverter Feedback
break;
case 0x010: //Time from inverter
break;
case 0x015: //Initialization message from inverter
send_tripower_init();
break;
case 0x017: //Initialization message from inverter 2
//send_tripower_init();
break;
default:
break;
}
}
void send_can_sma_tripower() {
unsigned long currentMillis = millis();
// Send CAN Message every 500ms
if (currentMillis - previousMillis500ms >= interval500ms) {
previousMillis500ms = currentMillis;
ESP32Can.CANWriteFrame(&SMA_00D); //Battery limits
ESP32Can.CANWriteFrame(&SMA_00F); // Battery state
ESP32Can.CANWriteFrame(&SMA_011); // Battery Energy
ESP32Can.CANWriteFrame(&SMA_013); // Battery Measurements
ESP32Can.CANWriteFrame(&SMA_014); // Battery Temperatures and cellvoltages
}
if (batteryAlarm) { //Non-cyclic
ESP32Can.CANWriteFrame(&SMA_005); // Battery Alarms 1
ESP32Can.CANWriteFrame(&SMA_007); // Battery Alarms 2
}
if (BMSevent) { //Non-cyclic
ESP32Can.CANWriteFrame(&SMA_006); // Battery Errorcode
ESP32Can.CANWriteFrame(&SMA_008); // Battery Events
}
}
void send_tripower_init() {
ESP32Can.CANWriteFrame(&SMA_015); // Battery Data 1
ESP32Can.CANWriteFrame(&SMA_016); // Battery Data 2
ESP32Can.CANWriteFrame(&SMA_017); // Battery Manufacturer
ESP32Can.CANWriteFrame(&SMA_018); // Battery Name
}

View file

@ -0,0 +1,35 @@
#ifndef SMA_CAN_TRIPOWER_H
#define SMA_CAN_TRIPOWER_H
#include <Arduino.h>
#include "../../USER_SETTINGS.h"
#include "../devboard/config.h" // Needed for all defines
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, true/false
void update_values_can_sma_tripower();
void send_can_sma_tripower();
void receive_can_sma_tripower(CAN_frame_t rx_frame);
void send_tripower_init();
#endif

View file

@ -281,29 +281,29 @@ CAN_frame_t SOFAR_7C0 = {.FIR = {.B =
void update_values_can_sofar() { //This function maps all the values fetched from battery CAN to the correct CAN messages
//Maxvoltage (eg 400.0V = 4000 , 16bits long) Charge Cutoff Voltage
SOFAR_351.data.u8[0] = (max_voltage >> 8);
SOFAR_351.data.u8[1] = (max_voltage & 0x00FF);
SOFAR_351.data.u8[0] = (system_max_design_voltage_dV >> 8);
SOFAR_351.data.u8[1] = (system_max_design_voltage_dV & 0x00FF);
//SOFAR_351.data.u8[2] = DC charge current limitation (Default 25.0A)
//SOFAR_351.data.u8[3] = DC charge current limitation
//SOFAR_351.data.u8[4] = DC discharge current limitation (Default 25.0A)
//SOFAR_351.data.u8[5] = DC discharge current limitation
//Minvoltage (eg 300.0V = 3000 , 16bits long) Discharge Cutoff Voltage
SOFAR_351.data.u8[6] = (min_voltage >> 8);
SOFAR_351.data.u8[7] = (min_voltage & 0x00FF);
SOFAR_351.data.u8[6] = (system_min_design_voltage_dV >> 8);
SOFAR_351.data.u8[7] = (system_min_design_voltage_dV & 0x00FF);
//SOC
SOFAR_355.data.u8[0] = (SOC / 100);
SOFAR_355.data.u8[2] = (StateOfHealth / 100);
SOFAR_355.data.u8[0] = (system_scaled_SOC_pptt / 100);
SOFAR_355.data.u8[2] = (system_SOH_pptt / 100);
//SOFAR_355.data.u8[6] = (AH_remaining >> 8);
//SOFAR_355.data.u8[7] = (AH_remaining & 0x00FF);
//Voltage (370.0)
SOFAR_356.data.u8[0] = (battery_voltage >> 8);
SOFAR_356.data.u8[1] = (battery_voltage & 0x00FF);
SOFAR_356.data.u8[2] = (battery_current >> 8);
SOFAR_356.data.u8[3] = (battery_current & 0x00FF);
SOFAR_356.data.u8[2] = (temperature_max >> 8);
SOFAR_356.data.u8[3] = (temperature_max & 0x00FF);
SOFAR_356.data.u8[0] = (system_battery_voltage_dV >> 8);
SOFAR_356.data.u8[1] = (system_battery_voltage_dV & 0x00FF);
SOFAR_356.data.u8[2] = (system_battery_current_dA >> 8);
SOFAR_356.data.u8[3] = (system_battery_current_dA & 0x00FF);
SOFAR_356.data.u8[2] = (system_temperature_max_dC >> 8);
SOFAR_356.data.u8[3] = (system_temperature_max_dC & 0x00FF);
}
void receive_can_sofar(CAN_frame_t rx_frame) {

View file

@ -6,24 +6,27 @@
#include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
// These parameters need to be mapped for the inverter
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
extern uint16_t capacity_Wh; //Wh, 0-60000
extern uint16_t remaining_capacity_Wh; //Wh, 0-60000
extern uint16_t max_target_discharge_power; //W, 0-60000
extern uint16_t max_target_charge_power; //W, 0-60000
extern uint8_t bms_status; //Enum, 0-5
extern uint8_t bms_char_dis_status; //Enum, 0-2
extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530)
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
extern uint16_t cell_max_voltage; //mV, 0-4350
extern uint16_t cell_min_voltage; //mV, 0-4350
extern uint8_t LEDcolor; //Enum, 0-10
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, true/false
extern uint16_t min_voltage;
extern uint16_t max_voltage;

View file

@ -1,12 +1,15 @@
#include "SOLAX-CAN.h"
#include "../devboard/utils/events.h"
/* Do not change code below unless you are sure what you are doing */
static uint16_t max_charge_rate_amp = 0;
static uint16_t max_discharge_rate_amp = 0;
static uint16_t temperature_average = 0;
static int STATE = BATTERY_ANNOUNCE;
static int16_t temperature_average = 0;
static uint8_t STATE = BATTERY_ANNOUNCE;
static unsigned long LastFrameTime = 0;
static int number_of_batteries = 1;
static uint8_t number_of_batteries = 1;
static uint16_t capped_capacity_Wh;
static uint16_t capped_remaining_capacity_Wh;
//CAN message translations from this amazing repository: https://github.com/rand12345/solax_can_bus
@ -122,64 +125,77 @@ void update_values_can_solax() { //This function maps all the values fetched fr
STATE = BATTERY_ANNOUNCE;
}
//Calculate the required values
temperature_average = ((temperature_max + temperature_min) / 2);
temperature_average = ((system_temperature_max_dC + system_temperature_min_dC) / 2);
//max_target_charge_power (30000W max)
if (SOC > 9999) //99.99%
{ //Additional safety incase SOC% is 100, then do not charge battery further
//system_max_charge_power_W (30000W max)
if (system_scaled_SOC_pptt > 9999) //99.99%
{ //Additional safety incase SOC% is 100, then do not charge battery further
max_charge_rate_amp = 0;
} else { //We can pass on the battery charge rate (in W) to the inverter (that takes A)
if (max_target_charge_power >= 30000) {
if (system_max_charge_power_W >= 30000) {
max_charge_rate_amp = 75; //Incase battery can take over 30kW, cap value to 75A
} else { //Calculate the W value into A
max_charge_rate_amp = (max_target_charge_power / (battery_voltage * 0.1)); // P/U = I
max_charge_rate_amp = (system_max_charge_power_W / (system_battery_voltage_dV * 0.1)); // P/U = I
}
}
//max_target_discharge_power (30000W max)
if (SOC < 100) //1.00%
{ //Additional safety incase SOC% is below 1, then do not charge battery further
//system_max_discharge_power_W (30000W max)
if (system_scaled_SOC_pptt < 100) //1.00%
{ //Additional safety incase SOC% is below 1, then do not charge battery further
max_discharge_rate_amp = 0;
} else { //We can pass on the battery discharge rate to the inverter
if (max_target_discharge_power >= 30000) {
if (system_max_discharge_power_W >= 30000) {
max_discharge_rate_amp = 75; //Incase battery can be charged with over 30kW, cap value to 75A
} else { //Calculate the W value into A
max_discharge_rate_amp = (max_target_discharge_power / (battery_voltage * 0.1)); // P/U = I
max_discharge_rate_amp = (system_max_discharge_power_W / (system_battery_voltage_dV * 0.1)); // P/U = I
}
}
// Batteries might be larger than uint16_t value can take
if (system_capacity_Wh > 65000) {
capped_capacity_Wh = 65000;
} else {
capped_capacity_Wh = system_capacity_Wh;
}
// Batteries might be larger than uint16_t value can take
if (system_remaining_capacity_Wh > 65000) {
capped_remaining_capacity_Wh = 65000;
} else {
capped_remaining_capacity_Wh = system_remaining_capacity_Wh;
}
//Put the values into the CAN messages
//BMS_Limits
SOLAX_1872.data.u8[0] = (uint8_t)max_voltage; //TODO: scaling OK?
SOLAX_1872.data.u8[1] = (max_voltage >> 8);
SOLAX_1872.data.u8[2] = (uint8_t)min_voltage; //TODO: scaling OK?
SOLAX_1872.data.u8[3] = (min_voltage >> 8);
SOLAX_1872.data.u8[4] = (uint8_t)(max_charge_rate_amp * 10); //TODO: scaling OK?
SOLAX_1872.data.u8[0] = (uint8_t)system_max_design_voltage_dV;
SOLAX_1872.data.u8[1] = (system_max_design_voltage_dV >> 8);
SOLAX_1872.data.u8[2] = (uint8_t)system_min_design_voltage_dV;
SOLAX_1872.data.u8[3] = (system_min_design_voltage_dV >> 8);
SOLAX_1872.data.u8[4] = (uint8_t)(max_charge_rate_amp * 10);
SOLAX_1872.data.u8[5] = ((max_charge_rate_amp * 10) >> 8);
SOLAX_1872.data.u8[6] = (uint8_t)(max_discharge_rate_amp * 10); //TODO: scaling OK?
SOLAX_1872.data.u8[6] = (uint8_t)(max_discharge_rate_amp * 10);
SOLAX_1872.data.u8[7] = ((max_discharge_rate_amp * 10) >> 8);
//BMS_PackData
SOLAX_1873.data.u8[0] = (uint8_t)battery_voltage; // OK
SOLAX_1873.data.u8[1] = (battery_voltage >> 8);
SOLAX_1873.data.u8[2] = (int8_t)battery_current; // OK, Signed (Active current in Amps x 10)
SOLAX_1873.data.u8[3] = (battery_current >> 8);
SOLAX_1873.data.u8[4] = (uint8_t)(SOC / 100); //SOC (100.00%)
SOLAX_1873.data.u8[0] = (uint8_t)system_battery_voltage_dV; // OK
SOLAX_1873.data.u8[1] = (system_battery_voltage_dV >> 8);
SOLAX_1873.data.u8[2] = (int8_t)system_battery_current_dA; // OK, Signed (Active current in Amps x 10)
SOLAX_1873.data.u8[3] = (system_battery_current_dA >> 8);
SOLAX_1873.data.u8[4] = (uint8_t)(system_scaled_SOC_pptt / 100); //SOC (100.00%)
//SOLAX_1873.data.u8[5] = //Seems like this is not required? Or shall we put SOC decimals here?
SOLAX_1873.data.u8[6] = (uint8_t)(remaining_capacity_Wh / 100); //TODO: scaling OK?
SOLAX_1873.data.u8[7] = ((remaining_capacity_Wh / 100) >> 8);
SOLAX_1873.data.u8[6] = (uint8_t)(capped_remaining_capacity_Wh / 100); //TODO: scaling OK?
SOLAX_1873.data.u8[7] = ((capped_remaining_capacity_Wh / 100) >> 8);
//BMS_CellData
SOLAX_1874.data.u8[0] = (uint8_t)temperature_max;
SOLAX_1874.data.u8[1] = (temperature_max >> 8);
SOLAX_1874.data.u8[2] = (uint8_t)temperature_min;
SOLAX_1874.data.u8[3] = (temperature_min >> 8);
SOLAX_1874.data.u8[0] = (int8_t)system_temperature_max_dC;
SOLAX_1874.data.u8[1] = (system_temperature_max_dC >> 8);
SOLAX_1874.data.u8[2] = (int8_t)system_temperature_min_dC;
SOLAX_1874.data.u8[3] = (system_temperature_min_dC >> 8);
SOLAX_1874.data.u8[4] =
(uint8_t)(cell_max_voltage); //TODO: scaling OK? Supposed to be alarm trigger absolute cell max?
SOLAX_1874.data.u8[5] = (cell_max_voltage >> 8);
(uint8_t)(system_cell_max_voltage_mV); //TODO: scaling OK? Supposed to be alarm trigger absolute cell max?
SOLAX_1874.data.u8[5] = (system_cell_max_voltage_mV >> 8);
SOLAX_1874.data.u8[6] =
(uint8_t)(cell_min_voltage); //TODO: scaling OK? Supposed to be alarm trigger absolute cell min?
SOLAX_1874.data.u8[7] = (cell_min_voltage >> 8);
(uint8_t)(system_cell_min_voltage_mV); //TODO: scaling OK? Supposed to be alarm trigger absolute cell min?
SOLAX_1874.data.u8[7] = (system_cell_min_voltage_mV >> 8);
//BMS_Status
SOLAX_1875.data.u8[0] = (uint8_t)temperature_average;
@ -188,11 +204,11 @@ void update_values_can_solax() { //This function maps all the values fetched fr
SOLAX_1875.data.u8[4] = (uint8_t)0; // Contactor Status 0=off, 1=on.
//BMS_PackTemps (strange name, since it has voltages?)
SOLAX_1876.data.u8[2] = (uint8_t)cell_max_voltage; //TODO: scaling OK?
SOLAX_1876.data.u8[3] = (cell_max_voltage >> 8);
SOLAX_1876.data.u8[2] = (uint8_t)system_cell_max_voltage_mV; //TODO: scaling OK?
SOLAX_1876.data.u8[3] = (system_cell_max_voltage_mV >> 8);
SOLAX_1876.data.u8[6] = (uint8_t)cell_min_voltage; //TODO: scaling OK?
SOLAX_1876.data.u8[7] = (cell_min_voltage >> 8);
SOLAX_1876.data.u8[6] = (uint8_t)system_cell_min_voltage_mV; //TODO: scaling OK?
SOLAX_1876.data.u8[7] = (system_cell_min_voltage_mV >> 8);
//Unknown
SOLAX_1877.data.u8[4] = (uint8_t)0x50; // Battery type
@ -201,11 +217,11 @@ void update_values_can_solax() { //This function maps all the values fetched fr
(uint8_t)0x02; // The above firmware version applies to:02 = Master BMS, 10 = S1, 20 = S2, 30 = S3, 40 = S4
//BMS_PackStats
SOLAX_1878.data.u8[0] = (uint8_t)(battery_voltage); //TODO: should this be max or current voltage?
SOLAX_1878.data.u8[1] = ((battery_voltage) >> 8);
SOLAX_1878.data.u8[0] = (uint8_t)(system_battery_voltage_dV); //TODO: should this be max or current voltage?
SOLAX_1878.data.u8[1] = ((system_battery_voltage_dV) >> 8);
SOLAX_1878.data.u8[4] = (uint8_t)capacity_Wh; //TODO: scaling OK?
SOLAX_1878.data.u8[5] = (capacity_Wh >> 8);
SOLAX_1878.data.u8[4] = (uint8_t)capped_capacity_Wh; //TODO: scaling OK?
SOLAX_1878.data.u8[5] = (capped_capacity_Wh >> 8);
// BMS_Answer
SOLAX_1801.data.u8[0] = 2;
@ -264,8 +280,10 @@ void receive_can_solax(CAN_frame_t rx_frame) {
CAN_WriteFrame(&SOLAX_1878);
// Message from the inverter to open contactor
// Byte 4 changes from 1 to 0
if (rx_frame.data.u64 == Contactor_Open_Payload)
if (rx_frame.data.u64 == Contactor_Open_Payload) {
set_event(EVENT_INVERTER_OPEN_CONTACTOR, 0);
STATE = BATTERY_ANNOUNCE;
}
break;
}
}

View file

@ -8,26 +8,27 @@
extern ACAN2515 can;
extern uint16_t SOC;
extern uint16_t StateOfHealth;
extern uint16_t battery_voltage;
extern uint16_t battery_current;
extern uint16_t capacity_Wh;
extern uint16_t remaining_capacity_Wh;
extern uint16_t max_target_discharge_power;
extern uint16_t max_target_charge_power;
extern uint8_t bms_status;
extern uint8_t bms_char_dis_status;
extern uint16_t stat_batt_power;
extern uint16_t temperature_min;
extern uint16_t temperature_max;
extern uint16_t CANerror;
extern uint16_t min_voltage;
extern uint16_t max_voltage;
extern uint16_t cell_max_voltage;
extern uint16_t cell_min_voltage;
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
extern uint32_t system_capacity_Wh; //Wh, 0-150000Wh
extern uint32_t system_remaining_capacity_Wh; //Wh, 0-150000Wh
extern int16_t system_temperature_min_dC; //C+1, -50.0 - 50.0
extern int16_t system_temperature_max_dC; //C+1, -50.0 - 50.0
extern int16_t system_active_power_W; //W, -32000 to 32000
extern int16_t system_battery_current_dA; //A+1, -1000 - 1000
extern uint16_t system_battery_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_max_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_min_design_voltage_dV; //V+1, 0-500.0 (0-5000)
extern uint16_t system_scaled_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_real_SOC_pptt; //SOC%, 0-100.00 (0-10000)
extern uint16_t system_SOH_pptt; //SOH%, 0-100.00 (0-10000)
extern uint16_t system_max_discharge_power_W; //W, 0-65000
extern uint16_t system_max_charge_power_W; //W, 0-65000
extern uint16_t system_cell_max_voltage_mV; //mV, 0-5000, Stores the highest cell millivolt value
extern uint16_t system_cell_min_voltage_mV; //mV, 0-5000, Stores the minimum cell millivolt value
extern uint16_t system_cellvoltages_mV[120]; //Array with all cell voltages in mV
extern uint8_t system_number_of_cells; //Total number of cell voltages, set by each battery
extern uint8_t system_bms_status; //Enum 0-5
extern bool batteryAllowsContactorClosing; //Bool, true/false
extern bool inverterAllowsContactorClosing; //Bool, true/false
// Timeout in milliseconds
#define SolaxTimeout 2000

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
#include "ESP32CAN.h"
#include <Arduino.h>
#include "../../devboard/utils/events.h"
int ESP32CAN::CANInit() {
return CAN_init();
@ -12,13 +13,14 @@ int ESP32CAN::CANWriteFrame(const CAN_frame_t* p_frame) {
tx_ok = (result == 0) ? true : false;
if (tx_ok == false) {
Serial.println("CAN failure! Check wires");
LEDcolor = 3;
set_event(EVENT_CAN_TX_FAILURE, 0);
start_time = millis();
} else {
clear_event(EVENT_CAN_TX_FAILURE);
}
} else {
if ((millis() - start_time) >= 2000) {
tx_ok = true;
LEDcolor = 0;
}
}
return result;

View file

@ -3,7 +3,6 @@
#include "../../lib/miwagner-ESP32-Arduino-CAN/CAN.h"
#include "../../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
extern uint8_t LEDcolor;
class ESP32CAN {
public:

15
cmake_clean.bat Normal file
View file

@ -0,0 +1,15 @@
@echo off
echo Cleaning up
rmdir /Q/S build
echo Creating new CMake build folder
mkdir build
cd build
echo Building CMake project
call cmake ..
call cmake --build .
echo Executing tests
for %%i in ("test\Debug\*.exe") do (
echo Running %%i
%%i
)
cd..

18
test/CMakeLists.txt Normal file
View file

@ -0,0 +1,18 @@
# Include the directory with your source files
include_directories(${CMAKE_SOURCE_DIR}/Software/src/devboard ${CMAKE_SOURCE_DIR}/Software/src/devboard/utils . )
# Create a variable to store the list of test files
file(GLOB TEST_SOURCES utils/*.cpp)
# Loop through each test source file and create an executable
foreach(TEST_SOURCE ${TEST_SOURCES})
# Extract the test name without extension
get_filename_component(TEST_NAME ${TEST_SOURCE} NAME_WE)
# Create an executable for the test
add_executable(${TEST_NAME} ${TEST_SOURCE} test_lib.cpp)
# Apply the target_compile_definitions for the test
target_compile_definitions(${TEST_NAME} PRIVATE UNIT_TEST)
endforeach()

209
test/microtest.h Normal file
View file

@ -0,0 +1,209 @@
//
// microtest.h
//
// URL: https://github.com/torpedro/microtest.h
// Author: Pedro Flemming (http://torpedro.com/)
// License: MIT License (https://github.com/torpedro/microtest.h/blob/master/LICENSE)
// Copyright (c) 2017 Pedro Flemming
//
// This is a small header-only C++ unit testing framework.
// It allows to define small unit tests with set of assertions available.
//
#ifndef __MICROTEST_H__
#define __MICROTEST_H__
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
////////////////
// Assertions //
////////////////
#define ASSERT(cond) ASSERT_TRUE(cond);
#define ASSERT_TRUE(cond) \
if (!(cond)) \
throw mt::AssertFailedException(#cond, __FILE__, __LINE__);
#define ASSERT_FALSE(cond) \
if (cond) \
throw mt::AssertFailedException(#cond, __FILE__, __LINE__);
#define ASSERT_NULL(value) ASSERT_TRUE(value == NULL);
#define ASSERT_NOTNULL(value) ASSERT_TRUE(value != NULL);
#define ASSERT_STREQ(a, b) \
if (std::string(a).compare(std::string(b)) != 0) { \
printf("%s{ info} %s", mt::yellow(), mt::def()); \
std::cout << "Actual values: " << a << " != " << b << std::endl; \
throw mt::AssertFailedException(#a " == " #b, __FILE__, __LINE__); \
}
#define ASSERT_STRNEQ(a, b) \
if (std::string(a).compare(std::string(b)) != = 0) { \
printf("%s{ info} %s", mt::yellow(), mt::def()); \
std::cout << "Actual values: " << a << " == " << b << std::endl; \
throw mt::AssertFailedException(#a " != " #b, __FILE__, __LINE__); \
}
#define ASSERT_EQ(a, b) \
if (a != b) { \
printf("%s{ info} %s", mt::yellow(), mt::def()); \
std::cout << "Actual values: " << a << " != " << b << std::endl; \
} \
ASSERT(a == b);
#define ASSERT_NEQ(a, b) \
if (a == b) { \
printf("%s{ info} %s", mt::yellow(), mt::def()); \
std::cout << "Actual values: " << a << " == " << b << std::endl; \
} \
ASSERT(a != b);
////////////////
// Unit Tests //
////////////////
#define TEST(name) \
void name(); \
namespace { \
bool __##name = mt::TestsManager::AddTest(name, #name); \
} \
void name()
///////////////
// Framework //
///////////////
namespace mt {
inline const char* red() {
return "\033[1;31m";
}
inline const char* green() {
return "\033[0;32m";
}
inline const char* yellow() {
return "\033[0;33m";
}
inline const char* def() {
return "\033[0m";
}
inline void printRunning(const char* message, FILE* file = stdout) {
fprintf(file, "%s{ running}%s %s\n", green(), def(), message);
}
inline void printOk(const char* message, FILE* file = stdout) {
fprintf(file, "%s{ ok}%s %s\n", green(), def(), message);
}
inline void printFailed(const char* message, FILE* file = stdout) {
fprintf(file, "%s{ failed} %s%s\n", red(), message, def());
}
// Exception that is thrown when an assertion fails.
class AssertFailedException : public std::exception {
public:
AssertFailedException(std::string description, std::string filepath, int line)
: std::exception(), description_(description), filepath_(filepath), line_(line){};
virtual const char* what() const throw() { return description_.c_str(); }
inline const char* getFilepath() { return filepath_.c_str(); }
inline int getLine() { return line_; }
protected:
std::string description_;
std::string filepath_;
int line_;
};
class TestsManager {
// Note: static initialization fiasco
// http://www.parashift.com/c++-faq-lite/static-init-order.html
// http://www.parashift.com/c++-faq-lite/static-init-order-on-first-use.html
public:
struct Test {
const char* name;
void (*fn)(void);
};
static std::vector<Test>& tests() {
static std::vector<Test> tests_;
return tests_;
}
// Adds a new test to the current set of tests.
// Returns false if a test with the same name already exists.
inline static bool AddTest(void (*fn)(void), const char* name) {
tests().push_back({name, fn});
return true;
}
// Run all tests that are registered.
// Returns the number of tests that failed.
inline static size_t RunAllTests(FILE* file = stdout) {
size_t num_failed = 0;
for (const Test& test : tests()) {
// Run the test.
// If an AsserFailedException is thrown, the test has failed.
try {
printRunning(test.name, file);
(*test.fn)();
printOk(test.name, file);
} catch (AssertFailedException& e) {
printFailed(test.name, file);
fprintf(file, " %sAssertion failed: %s%s\n", red(), e.what(), def());
fprintf(file, " %s%s:%d%s\n", red(), e.getFilepath(), e.getLine(), def());
++num_failed;
}
}
int return_code = (num_failed > 0) ? 1 : 0;
return return_code;
}
};
// Class that will capture the arguments passed to the program.
class Runtime {
public:
static const std::vector<std::string>& args(int argc = -1, char** argv = NULL) {
static std::vector<std::string> args_;
if (argc >= 0) {
for (int i = 0; i < argc; ++i) {
args_.push_back(argv[i]);
}
}
return args_;
}
};
} // namespace mt
#define TEST_MAIN() \
int main(int argc, char* argv[]) { \
mt::Runtime::args(argc, argv); \
\
size_t num_failed = mt::TestsManager::RunAllTests(stdout); \
if (num_failed == 0) { \
fprintf(stdout, "%s{ summary} All tests succeeded!%s\n", mt::green(), mt::def()); \
return 0; \
} else { \
double percentage = 100.0 * num_failed / mt::TestsManager::tests().size(); \
fprintf(stderr, "%s{ summary} %zu tests failed (%.2f%%)%s\n", mt::red(), num_failed, percentage, mt::def()); \
return -1; \
} \
}
#endif // __MICROTEST_H__

13
test/test_lib.cpp Normal file
View file

@ -0,0 +1,13 @@
#include "test_lib.h"
#include <cstdint>
#include "../Software/src/devboard/config.h"
MySerial Serial;
unsigned long testlib_millis = 0;
uint8_t bms_status = ACTIVE;
uint8_t LEDcolor = GREEN;

48
test/test_lib.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef __TEST_LIB_H__
#define __TEST_LIB_H__
#include <stdint.h>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include "config.h"
#include "microtest.h"
using namespace std;
class MySerial;
extern unsigned long testlib_millis;
extern MySerial Serial;
extern uint8_t bms_status;
extern uint8_t LEDcolor;
/* Mock millis() */
static inline unsigned long millis(void) {
return testlib_millis;
}
/* Mock Serial class */
class MySerial {
public:
size_t println(const char* s) {
return print(s, true); // Call print with newline argument true
}
size_t print(const char* s) {
return print(s, false); // Call print with newline argument false
}
private:
size_t print(const char* s, bool newline) {
size_t length = printf("%s", s); // Print the string without newline
if (newline) {
printf("\n"); // Add a newline if specified
length++; // Increment length to account for the added newline character
}
return length; // Return the total length printed
}
};
#endif

105
test/utils/events_test.cpp_ Normal file
View file

@ -0,0 +1,105 @@
// The test library must be included first!
#include "../test_lib.h"
#include "../../Software/src/devboard/config.h"
#include "../../Software/src/devboard/utils/timer.cpp"
class EEPROMClass {
public:
void begin(int s) {}
void writeUShort(int a, uint16_t d) {}
void commit(void) {}
uint16_t readUShort(int a) {}
template<typename T>
void get(int address, T &t) {}
template<typename T>
void put(int address, const T &t) {}
};
EEPROMClass EEPROM;
#include "../../Software/src/devboard/utils/events.cpp"
/* Local test variables */
bool elapsed = false;
/* Stubs */
void run_sequence_on_target(void) {}
/* Helper functions */
/* Test functions */
TEST(init_events_test) {
init_events();
for (uint8_t i = 0; i < EVENT_NOF_EVENTS; i++) {
ASSERT_EQ(events.entries[i].occurences, 0);
ASSERT_EQ(events.entries[i].data, 0);
ASSERT_EQ(events.entries[i].timestamp, 0);
}
}
TEST(update_event_time_test) {
// Reset
testlib_millis = 0;
events.time_seconds = 0;
init_events();
// No delta, so time shouldn't increase
update_event_time();
ASSERT_EQ(events.time_seconds, 0);
// Almost time to bump the seconds
testlib_millis = 999;
update_event_time();
ASSERT_EQ(events.time_seconds, 0);
// millis == 1000, so we should add a second
testlib_millis = 1000;
update_event_time();
ASSERT_EQ(events.time_seconds, 1);
// We shouldn't add more seconds until 2000 now
testlib_millis = 1999;
update_event_time();
ASSERT_EQ(events.time_seconds, 1);
testlib_millis = 2000;
update_event_time();
ASSERT_EQ(events.time_seconds, 2);
}
TEST(set_event_test) {
// Reset
init_events();
events.time_seconds = 0;
// Initially, the event should not have any data or occurences
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].data, 0);
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].occurences, 0);
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 0);
// Set current time and overvoltage event for cell 23 (RED color, bms_status == FAULT)
events.time_seconds = 345;
set_event(EVENT_CELL_OVER_VOLTAGE, 123);
// Ensure proper event data
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].data, 123);
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].occurences, 1);
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 345);
ASSERT_EQ(bms_status, FAULT);
}
TEST(events_message_test) {
set_event(EVENT_DUMMY_ERROR, 0); // Set dummy event with no data
ASSERT_STREQ("The dummy error event was set!", get_event_message_string(EVENT_DUMMY_ERROR));
}
TEST(events_level_test) {
init_events();
set_event(EVENT_DUMMY_ERROR, 0); // Set dummy event with no data
ASSERT_STREQ("ERROR", get_event_level_string(EVENT_DUMMY_ERROR));
}
TEST_MAIN();