diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index c0c1493e..7e2febc5 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -49,6 +49,7 @@ jobs: # - LUNA2000_MODBUS # - PYLON_CAN # - SMA_CAN +# - SMA_TRIPOWER_CAN # - SOFAR_CAN # - SOLAX_CAN diff --git a/.github/workflows/compile-all-combinations.yml b/.github/workflows/compile-all-combinations.yml index 7a507f58..e2c24be9 100644 --- a/.github/workflows/compile-all-combinations.yml +++ b/.github/workflows/compile-all-combinations.yml @@ -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 diff --git a/.github/workflows/compile-all-inverters.yml b/.github/workflows/compile-all-inverters.yml index 954e263c..f9504af9 100644 --- a/.github/workflows/compile-all-inverters.yml +++ b/.github/workflows/compile-all-inverters.yml @@ -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 diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..62c2f2a9 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -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 diff --git a/.gitignore b/.gitignore index 448c8864..309ff8ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ # Ignore any .vscode folder *.vscode/ -# Ignore any files in the build folder -Software/build/ \ No newline at end of file +# Ignore any files in any build folder +*build/ + +# Ignore .exe (unit tests) +*.exe +**/.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..7c9a3578 --- /dev/null +++ b/CMakeLists.txt @@ -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) diff --git a/Software/Software.ino b/Software/Software.ino index a7439f8b..d238f3aa 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -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(); } diff --git a/Software/USER_SETTINGS.cpp b/Software/USER_SETTINGS.cpp index bcc2a677..63fb6296 100644 --- a/Software/USER_SETTINGS.cpp +++ b/Software/USER_SETTINGS.cpp @@ -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 */ diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index e024dfd5..862e7b8b 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -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; diff --git a/Software/src/battery/BATTERIES.h b/Software/src/battery/BATTERIES.h index 2f823ab3..dfef51e3 100644 --- a/Software/src/battery/BATTERIES.h +++ b/Software/src/battery/BATTERIES.h @@ -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 diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index af54c2cd..c31d1035 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -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 diff --git a/Software/src/battery/BMW-I3-BATTERY.h b/Software/src/battery/BMW-I3-BATTERY.h index 6332b3c2..63203187 100644 --- a/Software/src/battery/BMW-I3-BATTERY.h +++ b/Software/src/battery/BMW-I3-BATTERY.h @@ -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 diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 84a3634d..a6ff7c90 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -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 diff --git a/Software/src/battery/CHADEMO-BATTERY.h b/Software/src/battery/CHADEMO-BATTERY.h index 6eccc66c..7675ad30 100644 --- a/Software/src/battery/CHADEMO-BATTERY.h +++ b/Software/src/battery/CHADEMO-BATTERY.h @@ -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 diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index 51ebdba9..24901cb0 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -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 diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h index 0a2af171..c035a208 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h @@ -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 diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 92677711..d9d7c448 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -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((static_cast(SOC) / 10000) * BATTERY_WH_MAX); + system_remaining_capacity_Wh = static_cast((static_cast(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 diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h index 3736e774..0029b0f0 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h @@ -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 diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index f8d4e29b..746b6705 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -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(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 diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.h b/Software/src/battery/NISSAN-LEAF-BATTERY.h index fa88a959..d437ed73 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.h +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.h @@ -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 diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index 47ab1f64..36a3be3a 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -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 diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.h b/Software/src/battery/RENAULT-KANGOO-BATTERY.h index 973c3df8..69e3f207 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.h +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.h @@ -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 diff --git a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp index d40f09cf..0aa0100a 100644 --- a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp @@ -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((static_cast(SOC) / 10000) * BATTERY_WH_MAX); + system_remaining_capacity_Wh = static_cast((static_cast(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 diff --git a/Software/src/battery/RENAULT-ZOE-BATTERY.h b/Software/src/battery/RENAULT-ZOE-BATTERY.h index aa45054f..853e9b42 100644 --- a/Software/src/battery/RENAULT-ZOE-BATTERY.h +++ b/Software/src/battery/RENAULT-ZOE-BATTERY.h @@ -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 diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index e2aaa66f..8458d55a 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -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 diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.h b/Software/src/battery/SANTA-FE-PHEV-BATTERY.h index c11d2023..da01cda1 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.h +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.h @@ -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 diff --git a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp index 0377766b..8e6e5dc7 100644 --- a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp +++ b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp @@ -1,5 +1,7 @@ -// SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp - +#include "BATTERIES.h" +#ifdef SERIAL_LINK_RECEIVER +#include +#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 diff --git a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h index a67b1026..8c4ab623 100644 --- a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h +++ b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h @@ -3,6 +3,8 @@ #ifndef SERIAL_LINK_RECEIVER_FROM_BATTERY_H #define SERIAL_LINK_RECEIVER_FROM_BATTERY_H +#define BATTERY_SELECTED + #include #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 diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index 8fbdf301..fc07d99d 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -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((static_cast(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((static_cast(SOC) / 10000) * BATTERY_WH_MAX); + system_remaining_capacity_Wh = + static_cast((static_cast(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 diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.h b/Software/src/battery/TESLA-MODEL-3-BATTERY.h index a10d6cc7..86e68f94 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.h +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.h @@ -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 diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index 8deac988..a4cec841 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -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 diff --git a/Software/src/battery/TEST-FAKE-BATTERY.h b/Software/src/battery/TEST-FAKE-BATTERY.h index 22f0a9c2..70c5b642 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.h +++ b/Software/src/battery/TEST-FAKE-BATTERY.h @@ -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 diff --git a/Software/src/charger/NISSAN-LEAF-CHARGER.cpp b/Software/src/charger/NISSAN-LEAF-CHARGER.cpp index 50e7a263..f4925b4e 100644 --- a/Software/src/charger/NISSAN-LEAF-CHARGER.cpp +++ b/Software/src/charger/NISSAN-LEAF-CHARGER.cpp @@ -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--; } diff --git a/Software/src/charger/NISSAN-LEAF-CHARGER.h b/Software/src/charger/NISSAN-LEAF-CHARGER.h index 691a7535..14152477 100644 --- a/Software/src/charger/NISSAN-LEAF-CHARGER.h +++ b/Software/src/charger/NISSAN-LEAF-CHARGER.h @@ -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); diff --git a/Software/src/devboard/config.h b/Software/src/devboard/config.h index c7e5c79c..19db5b4b 100644 --- a/Software/src/devboard/config.h +++ b/Software/src/devboard/config.h @@ -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 diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index f755420f..89a37123 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -4,6 +4,7 @@ #include #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(); + 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; } diff --git a/Software/src/devboard/mqtt/mqtt.h b/Software/src/devboard/mqtt/mqtt.h index b7b58c8f..4eeb307c 100644 --- a/Software/src/devboard/mqtt/mqtt.h +++ b/Software/src/devboard/mqtt/mqtt.h @@ -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 diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 3bf9035b..9bfb184b 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -1,82 +1,190 @@ #include "events.h" +#ifndef UNIT_TEST +#include +#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); + } } diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index e67824df..13e67179 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -4,52 +4,102 @@ #ifndef UNIT_TEST #include -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__ diff --git a/Software/src/devboard/utils/events_test_on_target.cpp b/Software/src/devboard/utils/events_test_on_target.cpp new file mode 100644 index 00000000..3ecf0ee0 --- /dev/null +++ b/Software/src/devboard/utils/events_test_on_target.cpp @@ -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 +} diff --git a/Software/src/devboard/utils/timer.cpp b/Software/src/devboard/utils/timer.cpp index ff051a03..7ce17e87 100644 --- a/Software/src/devboard/utils/timer.cpp +++ b/Software/src/devboard/utils/timer.cpp @@ -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(); +} diff --git a/Software/src/devboard/utils/timer.h b/Software/src/devboard/utils/timer.h index e330eb2f..84a7c423 100644 --- a/Software/src/devboard/utils/timer.h +++ b/Software/src/devboard/utils/timer.h @@ -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__ diff --git a/Software/src/devboard/webserver/cellmonitor_html.cpp b/Software/src/devboard/webserver/cellmonitor_html.cpp index 1051e87c..627fef3d 100644 --- a/Software/src/devboard/webserver/cellmonitor_html.cpp +++ b/Software/src/devboard/webserver/cellmonitor_html.cpp @@ -18,9 +18,9 @@ String cellmonitor_processor(const String& var) { // Display max, min, and deviation voltage values content += "
"; - content += "Max Voltage: " + String(cell_max_voltage) + " mV
"; - content += "Min Voltage: " + String(cell_min_voltage) + " mV
"; - int deviation = cell_max_voltage - cell_min_voltage; + content += "Max Voltage: " + String(system_cell_max_voltage_mV) + " mV
"; + content += "Min Voltage: " + String(system_cell_min_voltage_mV) + " mV
"; + int deviation = system_cell_max_voltage_mV - system_cell_min_voltage_mV; content += "Voltage Deviation: " + String(deviation) + " mV"; content += "
"; @@ -28,14 +28,14 @@ String cellmonitor_processor(const String& var) { content += "
"; 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) + "
" + String(cellvoltages[i]) + " mV"; + String cellContent = "Cell " + String(i + 1) + "
" + 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 = "" + cellContent + ""; } diff --git a/Software/src/devboard/webserver/cellmonitor_html.h b/Software/src/devboard/webserver/cellmonitor_html.h index 0a3d4aa9..17b81b69 100644 --- a/Software/src/devboard/webserver/cellmonitor_html.h +++ b/Software/src/devboard/webserver/cellmonitor_html.h @@ -4,9 +4,9 @@ #include #include -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 diff --git a/Software/src/devboard/webserver/events_html.cpp b/Software/src/devboard/webserver/events_html.cpp index 185d20c4..5890b217 100644 --- a/Software/src/devboard/webserver/events_html.cpp +++ b/Software/src/devboard/webserver/events_html.cpp @@ -1,5 +1,6 @@ #include "events_html.h" #include +#include "../utils/events.h" const char EVENTS_HTML_START[] = R"=====(
Event Type
Severity
Last Event
Count
Data
Message
@@ -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(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(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("
"); - content.concat("
" + String(get_event_enum_string(static_cast(i))) + "
"); - content.concat("
" + String(get_led_color_display_text(entries[i].led_color)) + "
"); - content.concat("
" + String(entries[i].timestamp) + "
"); - content.concat("
" + String(entries[i].occurences) + "
"); - content.concat("
" + String(entries[i].data) + "
"); - content.concat("
" + String(get_event_message(static_cast(i))) + "
"); + content.concat("
" + String(get_event_enum_string(event_handle)) + "
"); + content.concat("
" + String(get_event_level_string(event_handle)) + "
"); + content.concat("
" + String(event_pointer->timestamp) + "
"); + content.concat("
" + String(event_pointer->occurences) + "
"); + content.concat("
" + String(event_pointer->data) + "
"); + content.concat("
" + String(get_event_message_string(event_handle)) + "
"); content.concat(""); content.concat("
"); // End of event row } diff --git a/Software/src/devboard/webserver/events_html.h b/Software/src/devboard/webserver/events_html.h index a13f9b0c..073e305a 100644 --- a/Software/src/devboard/webserver/events_html.h +++ b/Software/src/devboard/webserver/events_html.h @@ -1,9 +1,7 @@ #ifndef EVENTS_H #define EVENTS_H -#include "../utils/events.h" - -extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; +#include /** * @brief Replaces placeholder with content section in web page diff --git a/Software/src/devboard/webserver/settings_html.cpp b/Software/src/devboard/webserver/settings_html.cpp index 35cbd113..dd824dc4 100644 --- a/Software/src/devboard/webserver/settings_html.cpp +++ b/Software/src/devboard/webserver/settings_html.cpp @@ -15,6 +15,8 @@ String settings_processor(const String& var) { // Show current settings with edit buttons and input fields content += "

Battery capacity: " + String(BATTERY_WH_MAX) + " Wh

"; + content += "

Rescale SOC: " + String(USE_SCALED_SOC) + + "

"; content += "

SOC max percentage: " + String(MAXPERCENTAGE / 10.0, 1) + "

"; content += "

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 += "
"; - float voltageFloat = static_cast(battery_voltage) / 10.0; // Convert to float and divide by 10 + float voltageFloat = static_cast(system_battery_voltage_dV) / 10.0; // Convert to float and divide by 10 content += "

Fake battery voltage: " + String(voltageFloat, 1) + " V

"; @@ -69,14 +71,26 @@ String settings_processor(const String& var) { content += "