From cb935e4cf8bfa1892dec40b2466f01d0e6eb723b Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Tue, 6 Feb 2024 20:06:07 +1100 Subject: [PATCH] Squashed commit of the following: commit a81133c6c01de9b140ce58da15565624e4385264 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue Feb 6 07:59:22 2024 +0100 Update timer_test.cpp commit aca520c5062777d8e718b3819925e120f0eb9ba0 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue Feb 6 07:55:00 2024 +0100 Added a test, new structure commit 1d09dacccb1424edc13fcc54bede3ed869047498 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:36:25 2024 +0100 Event setting commit 5edfd6cdec272f8c8dc7fdee3ee21b9a8bcd9507 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:16:46 2024 +0100 Restore to working order, success! commit 7f6eddca76a3da2e3f0f444c666bd63729f3eecc Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:15:54 2024 +0100 Testing for failed unit tests commit dcd8dcade98f3e855a5e6f52b025eb60fbd4e5be Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:13:25 2024 +0100 Name change commit f1bff798c4589dbec8626becf722f180be87a428 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:11:57 2024 +0100 Moved test files commit 933d11f932ab8502b65585560c680e8402dca681 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:03:34 2024 +0100 More UT commit 92fd0ac31b4552d13470a7f37bad31712ad5e54d Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:01:44 2024 +0100 Update unit-tests.yml commit 5785eeb111807eee48989477d3fd905974c08425 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 22:57:35 2024 +0100 Update unit-tests.yml commit f4fa1115c1df4a40113e4038119000477d0f23ba Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 22:55:27 2024 +0100 UT debugging commit 396f41ed7750e6b44b493d4d855b95ed9db26322 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 22:53:10 2024 +0100 Moved test folder previous location caused issues with building the main SW commit da8421dcff6a1be1636e9d40326117ba0362e7ec Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 22:42:13 2024 +0100 Unit test attempt commit 44c02745bf941637f0ef3bbb006e5064550533b2 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 14:10:12 2024 +0100 Improvements and unit tests commit 4d95fcfc31559e1777cac3357fcff13816b6a16e Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 01:19:37 2024 +0100 First commit --- .github/workflows/unit-tests.yml | 23 ++ .gitignore | 6 + CMakeLists.txt | 6 + Software/Software.ino | 5 + Software/USER_SETTINGS.h | 4 +- Software/src/battery/BMW-I3-BATTERY.cpp | 2 + Software/src/battery/CHADEMO-BATTERY.cpp | 2 + .../src/battery/IMIEV-CZERO-ION-BATTERY.cpp | 2 + .../src/battery/KIA-HYUNDAI-64-BATTERY.cpp | 7 + Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 12 + .../src/battery/RENAULT-KANGOO-BATTERY.cpp | 5 + Software/src/battery/RENAULT-ZOE-BATTERY.cpp | 5 + .../src/battery/SANTA-FE-PHEV-BATTERY.cpp | 2 + .../src/battery/TESLA-MODEL-3-BATTERY.cpp | 11 + Software/src/devboard/utils/events.cpp | 136 ++++++++++++ Software/src/devboard/utils/events.h | 35 +++ Software/src/devboard/utils/test_commands.txt | 1 + Software/src/devboard/utils/timer.cpp | 10 +- Software/src/devboard/utils/timer.h | 4 +- test/CMakeLists.txt | 18 ++ test/microtest.h | 209 ++++++++++++++++++ test/test_lib.cpp | 6 + test/test_lib.h | 43 ++++ test/utils/events_test.cpp | 78 +++++++ test/utils/timer_test.cpp | 32 +++ 25 files changed, 657 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/unit-tests.yml create mode 100644 CMakeLists.txt create mode 100644 Software/src/devboard/utils/events.cpp create mode 100644 Software/src/devboard/utils/events.h create mode 100644 Software/src/devboard/utils/test_commands.txt create mode 100644 test/CMakeLists.txt create mode 100644 test/microtest.h create mode 100644 test/test_lib.cpp create mode 100644 test/test_lib.h create mode 100644 test/utils/events_test.cpp create mode 100644 test/utils/timer_test.cpp diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..1381259a --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,23 @@ +name: Run Unit Tests + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Configure and build with CMake + run: | + mkdir build + cd build + cmake .. + cmake --build . + + - name: Run unit tests + run: | + cd build/test + find . -type f -executable -exec {} \; diff --git a/.gitignore b/.gitignore index 65ad4290..c32db3bf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,9 @@ # Ignore any files in the build folder Software/build/ + +# Ignore CMake build folder +build/ + +# Ignore unit tests +*.exe \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..6f0c4af6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.10) + +project(BatteryEmulator) + +# add_subdirectory(Software/src/devboard/utils) +add_subdirectory(test) diff --git a/Software/Software.ino b/Software/Software.ino index 2ec566d6..eb65591a 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -8,6 +8,7 @@ #include "src/battery/BATTERIES.h" #include "src/charger/CHARGERS.h" #include "src/devboard/config.h" +#include "src/devboard/utils/events.h" #include "src/inverter/INVERTERS.h" #include "src/lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h" #include "src/lib/eModbus-eModbus/Logging.h" @@ -129,6 +130,8 @@ void setup() { init_webserver(); #endif + init_events(); + init_CAN(); init_LED(); @@ -183,6 +186,7 @@ void loop() { { previousMillisUpdateVal = millis(); update_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus. + set_event(EVENT_DUMMY, (uint8_t)millis()); } // Output @@ -190,6 +194,7 @@ void loop() { #ifdef DUAL_CAN send_can2(); #endif + update_event_timestamps(); } // Initialization functions diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 0b4fe1c1..115a10a8 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -12,7 +12,7 @@ //#define CHADEMO_BATTERY //#define IMIEV_CZERO_ION_BATTERY //#define KIA_HYUNDAI_64_BATTERY -//#define NISSAN_LEAF_BATTERY +#define NISSAN_LEAF_BATTERY //#define RENAULT_KANGOO_BATTERY //#define RENAULT_ZOE_BATTERY //#define SANTA_FE_PHEV_BATTERY @@ -21,7 +21,7 @@ /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus -//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU +#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU //#define 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 diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 935da03e..40acb0e2 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -1,4 +1,5 @@ #include "BMW-I3-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -103,6 +104,7 @@ void update_values_i3_battery() { //This function maps all the values fetched v if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 929c0a15..84a3634d 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -1,4 +1,5 @@ #include "CHADEMO-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -107,6 +108,7 @@ void update_values_chademo_battery() { //This function maps all the values fetc bms_status = FAULT; errorCode = 7; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index f9a4d97d..51ebdba9 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -1,4 +1,5 @@ #include "IMIEV-CZERO-ION-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -109,6 +110,7 @@ void update_values_imiev_battery() { //This function maps all the values fetche if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 40c948cd..e1b27df7 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -1,4 +1,5 @@ #include "KIA-HYUNDAI-64-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -200,6 +201,7 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -207,11 +209,13 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value 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 @@ -220,14 +224,17 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (cell_max_voltage >= MAX_CELL_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + 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!"); + 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); } if (bms_status == FAULT) { //Incase we enter a critical fault state, zero out the allowed limits diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 17a28679..835487ee 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -2,6 +2,7 @@ #ifdef MQTT #include "../devboard/mqtt/mqtt.h" #endif +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -262,6 +263,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #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); } } @@ -311,6 +313,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #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 @@ -319,6 +322,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #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 @@ -328,6 +332,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched 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; @@ -342,6 +347,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #endif bms_status = FAULT; errorCode = 5; + set_event(EVENT_LOW_SOH, LB_StateOfHealth); max_target_discharge_power = 0; max_target_charge_power = 0; } @@ -355,6 +361,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched "disabled!"); #endif bms_status = FAULT; + set_event(EVENT_HVIL_FAILURE, 0); errorCode = 6; SOC = 0; max_target_discharge_power = 0; @@ -369,6 +376,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control."); #endif + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -380,6 +388,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #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); } /*Finally print out values to serial if configured to do so*/ @@ -615,6 +624,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #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) { @@ -623,6 +633,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #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; @@ -630,6 +641,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); #endif + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } break; } diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index 4f818878..47ab1f64 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -1,4 +1,5 @@ #include "RENAULT-KANGOO-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -126,6 +127,7 @@ void update_values_kangoo_battery() { //This function maps all the values fetch if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -133,14 +135,17 @@ void update_values_kangoo_battery() { //This function maps all the values fetch 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); } #ifdef DEBUG_VIA_USB diff --git a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp index b65b71ec..d40f09cf 100644 --- a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp @@ -1,4 +1,5 @@ #include "RENAULT-ZOE-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -87,6 +88,7 @@ void update_values_zoe_battery() { //This function maps all the values fetched if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -94,14 +96,17 @@ void update_values_zoe_battery() { //This function maps all the values fetched 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); } #ifdef DEBUG_VIA_USB diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index 2959a684..e2aaa66f 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -1,4 +1,5 @@ #include "SANTA-FE-PHEV-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -84,6 +85,7 @@ void update_values_santafe_phev_battery() { //This function maps all the values if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index 59aa7c18..fb05806a 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -1,4 +1,5 @@ #include "TESLA-MODEL-3-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -231,6 +232,7 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (!stillAliveCAN) { bms_status = FAULT; Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { stillAliveCAN--; } @@ -238,6 +240,7 @@ void update_values_tesla_model_3_battery() { //This function maps all the value 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); } cell_deviation_mV = (cell_max_v - cell_min_v); @@ -258,12 +261,14 @@ void update_values_tesla_model_3_battery() { //This function maps all the value 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); } } //Check if BMS is in need of recalibration if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) { Serial.println("Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); + set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy); LEDcolor = YELLOW; } @@ -271,27 +276,33 @@ void update_values_tesla_model_3_battery() { //This function maps all the value 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); } 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); } 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); } } 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); } 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); } 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); } } diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp new file mode 100644 index 00000000..06963830 --- /dev/null +++ b/Software/src/devboard/utils/events.cpp @@ -0,0 +1,136 @@ +#include "events.h" + +#include "../../../USER_SETTINGS.h" +#include "../config.h" + +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; + +static EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; +static unsigned long previous_millis = 0; +static uint32_t time_seconds = 0; +static uint8_t total_led_color = GREEN; +static char event_message[256]; + +/* Local function prototypes */ +static void set_event_message(EVENTS_ENUM_TYPE event); +static void update_led_color(EVENTS_ENUM_TYPE event); + +/* Exported functions */ +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 + } + + // YELLOW 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; +} + +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(event_message); +#endif +} + +void update_event_timestamps(void) { + unsigned long new_millis = millis(); + if (new_millis - previous_millis >= 1000) { + time_seconds++; + previous_millis = new_millis; + } +} + +/* Local functions */ +static void update_led_color(EVENTS_ENUM_TYPE event) { + total_led_color = (total_led_color == RED) ? RED : entries[event].led_color; +} + +static void set_event_message(EVENTS_ENUM_TYPE event) { + switch (event) { + case EVENT_CAN_FAILURE: + snprintf(event_message, sizeof(event_message), + "No CAN communication detected for 60s. Shutting down battery control."); + break; + case EVENT_CAN_WARNING: + snprintf(event_message, sizeof(event_message), + "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"); + break; + case EVENT_WATER_INGRESS: + snprintf(event_message, sizeof(event_message), + "Water leakage inside battery detected. Operation halted. Inspect battery!"); + break; + case EVENT_12V_LOW: + snprintf(event_message, sizeof(event_message), + "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"); + break; + case EVENT_SOC_PLAUSIBILITY_ERROR: + snprintf(event_message, sizeof(event_message), "ERROR: SOC% reported by battery not plausible. Restart battery!"); + break; + case EVENT_KWH_PLAUSIBILITY_ERROR: + snprintf(event_message, sizeof(event_message), + "Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); + break; + case EVENT_BATTERY_CHG_STOP_REQ: + snprintf(event_message, sizeof(event_message), + "ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"); + break; + case EVENT_BATTERY_DISCHG_STOP_REQ: + snprintf(event_message, sizeof(event_message), + "ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"); + break; + case EVENT_BATTERY_CHG_DISCHG_STOP_REQ: + snprintf(event_message, sizeof(event_message), + "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"); + break; + case EVENT_LOW_SOH: + snprintf( + event_message, sizeof(event_message), + "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery."); + break; + case EVENT_HVIL_FAILURE: + snprintf(event_message, sizeof(event_message), + "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be " + "disabled!"); + break; + case EVENT_INTERNAL_OPEN_FAULT: + snprintf(event_message, sizeof(event_message), + "ERROR: High voltage cable removed while battery running. Opening contactors!"); + break; + case EVENT_CELL_UNDER_VOLTAGE: + snprintf(event_message, sizeof(event_message), + "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + break; + case EVENT_CELL_OVER_VOLTAGE: + snprintf(event_message, sizeof(event_message), + "ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + break; + case EVENT_CELL_DEVIATION_HIGH: + snprintf(event_message, sizeof(event_message), "ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + break; + case EVENT_UNKNOWN_EVENT_SET: + snprintf(event_message, sizeof(event_message), "An unknown event was set! Review your code!"); + break; + case EVENT_DUMMY: + snprintf(event_message, sizeof(event_message), "The dummy event was set!"); // Don't change this event message! + break; + default: + break; + } +} diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h new file mode 100644 index 00000000..58abb58e --- /dev/null +++ b/Software/src/devboard/utils/events.h @@ -0,0 +1,35 @@ +#ifndef __EVENTS_H__ +#define __EVENTS_H__ + +#ifndef UNIT_TEST +#include +#endif + +#include + +typedef enum { + EVENT_CAN_FAILURE = 0u, + EVENT_CAN_WARNING, + EVENT_WATER_INGRESS, + EVENT_12V_LOW, + EVENT_SOC_PLAUSIBILITY_ERROR, + EVENT_KWH_PLAUSIBILITY_ERROR, + EVENT_BATTERY_CHG_STOP_REQ, + EVENT_BATTERY_DISCHG_STOP_REQ, + EVENT_BATTERY_CHG_DISCHG_STOP_REQ, + EVENT_LOW_SOH, + EVENT_HVIL_FAILURE, + EVENT_INTERNAL_OPEN_FAULT, + EVENT_CELL_UNDER_VOLTAGE, + EVENT_CELL_OVER_VOLTAGE, + EVENT_CELL_DEVIATION_HIGH, + EVENT_UNKNOWN_EVENT_SET, + EVENT_DUMMY, + EVENT_NOF_EVENTS +} EVENTS_ENUM_TYPE; + +void init_events(void); +void set_event(EVENTS_ENUM_TYPE event, uint8_t data); +void update_event_timestamps(void); + +#endif // __MYTIMER_H__ diff --git a/Software/src/devboard/utils/test_commands.txt b/Software/src/devboard/utils/test_commands.txt new file mode 100644 index 00000000..af3b68f6 --- /dev/null +++ b/Software/src/devboard/utils/test_commands.txt @@ -0,0 +1 @@ +g++ events_test.cpp ../../../test/test_lib.cpp -o events_test.exe -DUNIT_TEST -I. \ No newline at end of file diff --git a/Software/src/devboard/utils/timer.cpp b/Software/src/devboard/utils/timer.cpp index 5b4d59bf..ff051a03 100644 --- a/Software/src/devboard/utils/timer.cpp +++ b/Software/src/devboard/utils/timer.cpp @@ -1,11 +1,13 @@ #include "timer.h" -MyTimer::MyTimer(unsigned long interval) : interval(interval), previousMillis(0) {} +MyTimer::MyTimer(unsigned long interval) : interval(interval) { + previous_millis = millis(); +} bool MyTimer::elapsed() { - unsigned long currentMillis = millis(); - if (currentMillis - previousMillis >= interval) { - previousMillis = currentMillis; + unsigned long current_millis = millis(); + if (current_millis - previous_millis >= interval) { + previous_millis = current_millis; return true; } return false; diff --git a/Software/src/devboard/utils/timer.h b/Software/src/devboard/utils/timer.h index 829ea1e5..e330eb2f 100644 --- a/Software/src/devboard/utils/timer.h +++ b/Software/src/devboard/utils/timer.h @@ -1,7 +1,9 @@ #ifndef __MYTIMER_H__ #define __MYTIMER_H__ +#ifndef UNIT_TEST #include +#endif class MyTimer { public: @@ -12,7 +14,7 @@ class MyTimer { private: unsigned long interval; - unsigned long previousMillis; + unsigned long previous_millis; }; #endif // __MYTIMER_H__ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..6d63e2e3 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,18 @@ +# Include the directory with your source files +include_directories(${CMAKE_SOURCE_DIR}/Software/src/devboard/utils .) + +# Create a variable to store the list of test files +file(GLOB TEST_SOURCES utils/*.cpp) + +# Loop through each test source file and create an executable +foreach(TEST_SOURCE ${TEST_SOURCES}) + # Extract the test name without extension + get_filename_component(TEST_NAME ${TEST_SOURCE} NAME_WE) + + # Create an executable for the test + add_executable(${TEST_NAME} ${TEST_SOURCE} test_lib.cpp) + + # Apply the target_compile_definitions for the test + target_compile_definitions(${TEST_NAME} PRIVATE UNIT_TEST) + +endforeach() diff --git a/test/microtest.h b/test/microtest.h new file mode 100644 index 00000000..36c73aa1 --- /dev/null +++ b/test/microtest.h @@ -0,0 +1,209 @@ +// +// microtest.h +// +// URL: https://github.com/torpedro/microtest.h +// Author: Pedro Flemming (http://torpedro.com/) +// License: MIT License (https://github.com/torpedro/microtest.h/blob/master/LICENSE) +// Copyright (c) 2017 Pedro Flemming +// +// This is a small header-only C++ unit testing framework. +// It allows to define small unit tests with set of assertions available. +// +#ifndef __MICROTEST_H__ +#define __MICROTEST_H__ + +#include +#include +#include +#include + +//////////////// +// Assertions // +//////////////// + +#define ASSERT(cond) ASSERT_TRUE(cond); + +#define ASSERT_TRUE(cond) \ + if (!(cond)) \ + throw mt::AssertFailedException(#cond, __FILE__, __LINE__); + +#define ASSERT_FALSE(cond) \ + if (cond) \ + throw mt::AssertFailedException(#cond, __FILE__, __LINE__); + +#define ASSERT_NULL(value) ASSERT_TRUE(value == NULL); + +#define ASSERT_NOTNULL(value) ASSERT_TRUE(value != NULL); + +#define ASSERT_STREQ(a, b) \ + if (std::string(a).compare(std::string(b)) != 0) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " != " << b << std::endl; \ + throw mt::AssertFailedException(#a " == " #b, __FILE__, __LINE__); \ + } + +#define ASSERT_STRNEQ(a, b) \ + if (std::string(a).compare(std::string(b)) != = 0) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " == " << b << std::endl; \ + throw mt::AssertFailedException(#a " != " #b, __FILE__, __LINE__); \ + } + +#define ASSERT_EQ(a, b) \ + if (a != b) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " != " << b << std::endl; \ + } \ + ASSERT(a == b); + +#define ASSERT_NEQ(a, b) \ + if (a == b) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " == " << b << std::endl; \ + } \ + ASSERT(a != b); + +//////////////// +// Unit Tests // +//////////////// + +#define TEST(name) \ + void name(); \ + namespace { \ + bool __##name = mt::TestsManager::AddTest(name, #name); \ + } \ + void name() + +/////////////// +// Framework // +/////////////// + +namespace mt { + +inline const char* red() { + return "\033[1;31m"; +} + +inline const char* green() { + return "\033[0;32m"; +} + +inline const char* yellow() { + return "\033[0;33m"; +} + +inline const char* def() { + return "\033[0m"; +} + +inline void printRunning(const char* message, FILE* file = stdout) { + fprintf(file, "%s{ running}%s %s\n", green(), def(), message); +} + +inline void printOk(const char* message, FILE* file = stdout) { + fprintf(file, "%s{ ok}%s %s\n", green(), def(), message); +} + +inline void printFailed(const char* message, FILE* file = stdout) { + fprintf(file, "%s{ failed} %s%s\n", red(), message, def()); +} + +// Exception that is thrown when an assertion fails. +class AssertFailedException : public std::exception { + public: + AssertFailedException(std::string description, std::string filepath, int line) + : std::exception(), description_(description), filepath_(filepath), line_(line){}; + + virtual const char* what() const throw() { return description_.c_str(); } + + inline const char* getFilepath() { return filepath_.c_str(); } + + inline int getLine() { return line_; } + + protected: + std::string description_; + std::string filepath_; + int line_; +}; + +class TestsManager { + // Note: static initialization fiasco + // http://www.parashift.com/c++-faq-lite/static-init-order.html + // http://www.parashift.com/c++-faq-lite/static-init-order-on-first-use.html + public: + struct Test { + const char* name; + void (*fn)(void); + }; + + static std::vector& tests() { + static std::vector tests_; + return tests_; + } + + // Adds a new test to the current set of tests. + // Returns false if a test with the same name already exists. + inline static bool AddTest(void (*fn)(void), const char* name) { + tests().push_back({name, fn}); + return true; + } + + // Run all tests that are registered. + // Returns the number of tests that failed. + inline static size_t RunAllTests(FILE* file = stdout) { + size_t num_failed = 0; + + for (const Test& test : tests()) { + // Run the test. + // If an AsserFailedException is thrown, the test has failed. + try { + printRunning(test.name, file); + + (*test.fn)(); + + printOk(test.name, file); + + } catch (AssertFailedException& e) { + printFailed(test.name, file); + fprintf(file, " %sAssertion failed: %s%s\n", red(), e.what(), def()); + fprintf(file, " %s%s:%d%s\n", red(), e.getFilepath(), e.getLine(), def()); + ++num_failed; + } + } + + int return_code = (num_failed > 0) ? 1 : 0; + return return_code; + } +}; + +// Class that will capture the arguments passed to the program. +class Runtime { + public: + static const std::vector& args(int argc = -1, char** argv = NULL) { + static std::vector args_; + if (argc >= 0) { + for (int i = 0; i < argc; ++i) { + args_.push_back(argv[i]); + } + } + return args_; + } +}; +} // namespace mt + +#define TEST_MAIN() \ + int main(int argc, char* argv[]) { \ + mt::Runtime::args(argc, argv); \ + \ + size_t num_failed = mt::TestsManager::RunAllTests(stdout); \ + if (num_failed == 0) { \ + fprintf(stdout, "%s{ summary} All tests succeeded!%s\n", mt::green(), mt::def()); \ + return 0; \ + } else { \ + double percentage = 100.0 * num_failed / mt::TestsManager::tests().size(); \ + fprintf(stderr, "%s{ summary} %lu tests failed (%.2f%%)%s\n", mt::red(), num_failed, percentage, mt::def()); \ + return -1; \ + } \ + } + +#endif // __MICROTEST_H__ diff --git a/test/test_lib.cpp b/test/test_lib.cpp new file mode 100644 index 00000000..8ed9c2f5 --- /dev/null +++ b/test/test_lib.cpp @@ -0,0 +1,6 @@ +#include "test_lib.h" +#include + +MySerial Serial; + +unsigned long testlib_millis = 0; diff --git a/test/test_lib.h b/test/test_lib.h new file mode 100644 index 00000000..ccca3ca4 --- /dev/null +++ b/test/test_lib.h @@ -0,0 +1,43 @@ +#ifndef __TEST_LIB_H__ +#define __TEST_LIB_H__ + +#include +#include +#include + +#include "microtest.h" + +class MySerial; + +extern unsigned long testlib_millis; + +/* Mock millis() */ +static inline unsigned long millis(void) { + return testlib_millis; +} + +/* Mock Serial class */ +class MySerial { + public: + size_t println(const char* s) { + return print(s, true); // Call print with newline argument true + } + + size_t print(const char* s) { + return print(s, false); // Call print with newline argument false + } + + private: + size_t print(const char* s, bool newline) { + size_t length = printf("%s", s); // Print the string without newline + if (newline) { + printf("\n"); // Add a newline if specified + length++; // Increment length to account for the added newline character + } + return length; // Return the total length printed + } +}; + +extern MySerial Serial; + +#endif diff --git a/test/utils/events_test.cpp b/test/utils/events_test.cpp new file mode 100644 index 00000000..1d6c0748 --- /dev/null +++ b/test/utils/events_test.cpp @@ -0,0 +1,78 @@ +// The test library must be included first! +#include "test_lib.h" + +#include "events.cpp" + +/* Helper functions */ +static void reset_event_msg(void) { + snprintf(event_message, sizeof(event_message), ""); +} + +TEST(init_events_test) { + init_events(); + + for (uint8_t i = 0; i < EVENT_NOF_EVENTS; i++) { + ASSERT_EQ(entries[i].occurences, 0); + } +} + +TEST(update_event_timestamps_test) { + // Reset + init_events(); + time_seconds = 0; + + // No delta, so time shouldn't increase + testlib_millis = 0; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 0); + + // Almost time to bump the seconds + testlib_millis = 999; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 0); + ASSERT_EQ(previous_millis, 0); + + // millis == 1000, so we should add a second + testlib_millis = 1000; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 1); + ASSERT_EQ(previous_millis, 1000); + + // We shouldn't add more seconds until 2000 now + testlib_millis = 1999; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 1); + ASSERT_EQ(previous_millis, 1000); + testlib_millis = 2000; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 2); + ASSERT_EQ(previous_millis, 2000); +} + +TEST(set_event_test) { + // Reset + init_events(); + time_seconds = 0; + + // Initially, the event should not have any data or occurences + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].data, 0); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].occurences, 0); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 0); + // Set current time and overvoltage event for cell 23 + time_seconds = 345; + set_event(EVENT_CELL_OVER_VOLTAGE, 123); + // Ensure proper event data + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].data, 123); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].occurences, 1); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 345); +} + +TEST(event_message_test) { + reset_event_msg(); + + set_event(EVENT_DUMMY, 0); // Set dummy event with no data + + ASSERT_STREQ("The dummy event was set!", event_message); +} + +TEST_MAIN(); diff --git a/test/utils/timer_test.cpp b/test/utils/timer_test.cpp new file mode 100644 index 00000000..96e9b142 --- /dev/null +++ b/test/utils/timer_test.cpp @@ -0,0 +1,32 @@ +// The test library must be included first! +#include "../test_lib.h" + +#include "timer.cpp" + +/* Helper functions */ + +/* Test functions */ + +TEST(timer_test) { + unsigned long test_interval = 10; + + testlib_millis = 0; + MyTimer timer(test_interval); + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = test_interval - 1; + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = test_interval; + ASSERT_EQ(timer.elapsed(), true); + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = 2 * test_interval - 1; + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = 2 * test_interval; + ASSERT_EQ(timer.elapsed(), true); + ASSERT_EQ(timer.elapsed(), false); +} + +TEST_MAIN();