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..295b8fe6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ # 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 \ No newline at end of file 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 c038af2f..5578ed38 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -96,6 +96,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 @@ -149,6 +150,9 @@ void setup() { #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 @@ -187,7 +191,7 @@ void loop() { previousMillisUpdateVal = millis(); 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 +200,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 @@ -557,30 +567,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. diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 4ba402a6..373922c8 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 @@ -37,10 +37,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 /* 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" diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 40acb0e2..4e02062e 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -55,8 +55,6 @@ static uint16_t DC_link = 0; static int16_t Battery_Power = 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 - //Calculate the SOC% value to send to inverter Calculated_SOC = (Display_SOC * 10); //Increase decimal amount Calculated_SOC = @@ -102,9 +100,7 @@ void update_values_i3_battery() { //This function maps all the values fetched v /* 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--; } diff --git a/Software/src/battery/BMW-I3-BATTERY.h b/Software/src/battery/BMW-I3-BATTERY.h index 6332b3c2..99bc8a8c 100644 --- a/Software/src/battery/BMW-I3-BATTERY.h +++ b/Software/src/battery/BMW-I3-BATTERY.h @@ -18,7 +18,6 @@ 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; diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 84a3634d..fbb5aa72 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -90,7 +90,6 @@ 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 SOC = ChargingRate; @@ -105,10 +104,8 @@ void update_values_chademo_battery() { //This function maps all the values fetc /* 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--; } diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index 51ebdba9..54553f24 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -41,8 +41,6 @@ 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 - SOC = (uint16_t)(BMU_SOC * 100); //increase BMU_SOC range from 0-100 -> 100.00 battery_voltage = (uint16_t)(BMU_PackVoltage * 10); // Multiply by 10 and cast to uint16_t @@ -108,9 +106,7 @@ void update_values_imiev_battery() { //This function maps all the values fetche /* 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--; } diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h index 0a2af171..d72ba04a 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.h @@ -18,7 +18,6 @@ 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; @@ -28,7 +27,6 @@ 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; void update_values_imiev_battery(); void receive_can_imiev_battery(CAN_frame_t rx_frame); diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 92677711..cc02cc03 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -195,26 +195,18 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value cell_min_voltage = CellVoltMin_mV; - bms_status = ACTIVE; //Startout in active mode. Then check safeties - /* 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--; } 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); } @@ -222,18 +214,12 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value cell_deviation_mV = (cell_max_voltage - cell_min_voltage); 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); } diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h index 3736e774..ff357842 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.h @@ -20,7 +20,6 @@ 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) @@ -29,7 +28,6 @@ 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 diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index f8d4e29b..ec2b3afb 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -246,8 +246,6 @@ void update_values_leaf_battery() { /* This function maps all the values fetched cellvoltages[i] = cell_voltages[i]; } - bms_status = ACTIVE; //Startout in active mode - /*Extra safety functions below*/ if (LB_GIDS < 10) //800Wh left in battery { //Battery is running abnormally low, some discharge logic might have failed. Zero it all out. @@ -259,11 +257,9 @@ void update_values_leaf_battery() { /* This function maps all the values fetched if (battery_voltage > (ABSOLUTE_MAX_VOLTAGE - 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); } } @@ -308,30 +304,17 @@ 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: @@ -341,11 +324,6 @@ void update_values_leaf_battery() { /* This function maps all the values fetched 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; @@ -355,12 +333,6 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #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; @@ -371,12 +343,8 @@ void update_values_leaf_battery() { /* This function maps all the values fetched /* 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--; } @@ -384,11 +352,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched 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); } /*Finally print out values to serial if configured to do so*/ @@ -620,27 +584,15 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { cell_min_voltage = 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; diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.h b/Software/src/battery/NISSAN-LEAF-BATTERY.h index fa88a959..34fdf23d 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.h +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.h @@ -18,14 +18,12 @@ 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 diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index 47ab1f64..eb36ec5a 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -58,8 +58,6 @@ static const int interval100 = 100; // interval (ms) at which send CAN Messag 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 - StateOfHealth = (LB_SOH * 100); //Increase range from 99% -> 99.00% //Calculate the SOC% value to send to Fronius @@ -125,26 +123,18 @@ void update_values_kangoo_battery() { //This function maps all the values fetch /* 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--; } 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); } diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.h b/Software/src/battery/RENAULT-KANGOO-BATTERY.h index 973c3df8..105f60a9 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.h +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.h @@ -23,7 +23,6 @@ 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) @@ -31,7 +30,6 @@ extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 funct 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 diff --git a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp index d40f09cf..f52f209d 100644 --- a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp @@ -43,8 +43,6 @@ static const int interval100 = 100; // interval (ms) at which send CAN Messag 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 - StateOfHealth = (LB_SOH * 100); //Increase range from 99% -> 99.00% //Calculate the SOC% value to send to Fronius @@ -86,26 +84,18 @@ void update_values_zoe_battery() { //This function maps all the values fetched /* 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--; } 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); } diff --git a/Software/src/battery/RENAULT-ZOE-BATTERY.h b/Software/src/battery/RENAULT-ZOE-BATTERY.h index aa45054f..7a2e46c4 100644 --- a/Software/src/battery/RENAULT-ZOE-BATTERY.h +++ b/Software/src/battery/RENAULT-ZOE-BATTERY.h @@ -23,7 +23,6 @@ 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) @@ -31,7 +30,6 @@ extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 funct 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 diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index e2aaa66f..24f2e2b1 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -79,13 +79,9 @@ void update_values_santafe_phev_battery() { //This function maps all the values temperature_max; - bms_status = ACTIVE; //Startout in active mode, then check safeties - /* 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--; } diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.h b/Software/src/battery/SANTA-FE-PHEV-BATTERY.h index c11d2023..f2d977f8 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.h +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.h @@ -18,7 +18,6 @@ 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; diff --git a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp index 0377766b..b377a1f8 100644 --- a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp +++ b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp @@ -1,6 +1,8 @@ // SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp #include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h" +#include +#include "../devboard/utils/events.h" #define INVERTER_SEND_NUM_VARIABLES 1 #define INVERTER_RECV_NUM_VARIABLES 16 @@ -35,7 +37,6 @@ void __getData() { 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); @@ -46,8 +47,10 @@ void __getData() { batteryAllowsContactorClosing = (uint16_t)dataLinkReceive.getReceivedData(16); batteryFault = false; - if (_bms_status == FAULT) + if (_bms_status == FAULT) { batteryFault = true; + set_event(EVENT_SERIAL_TRANSMITTER_FAILURE, 0); + } } void updateData() { @@ -116,12 +119,12 @@ void manageSerialLinkReceiver() { if (minutesLost < 4) { max_target_charge_power = (lastGoodMaxCharge * (4 - minutesLost)) / 4; max_target_discharge_power = (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; + set_event(EVENT_SERIAL_RX_FAILURE, uint8_t(min(minutesLost, 255uL))); //----- Throw Error } // report Lost data & Max charge / Discharge reductions diff --git a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h index a67b1026..089276a5 100644 --- a/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h +++ b/Software/src/battery/SERIAL-LINK-RECEIVER-FROM-BATTERY.h @@ -30,7 +30,6 @@ extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 funct 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; diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index 8fbdf301..9071815d 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -226,20 +226,14 @@ void update_values_tesla_model_3_battery() { //This function maps all the value /* 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--; } 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); } @@ -259,8 +253,6 @@ void update_values_tesla_model_3_battery() { //This function maps all the value 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); } } @@ -270,7 +262,6 @@ 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); @@ -278,34 +269,22 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (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); } 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/battery/TESLA-MODEL-3-BATTERY.h b/Software/src/battery/TESLA-MODEL-3-BATTERY.h index a10d6cc7..ed2ba9c1 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.h +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.h @@ -21,7 +21,6 @@ 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) @@ -30,7 +29,6 @@ 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; diff --git a/Software/src/battery/TEST-FAKE-BATTERY.cpp b/Software/src/battery/TEST-FAKE-BATTERY.cpp index 8deac988..dcce6e04 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.cpp +++ b/Software/src/battery/TEST-FAKE-BATTERY.cpp @@ -17,11 +17,7 @@ void print_units(char* header, int value, char* 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 - - LEDcolor = TEST_ALL_COLORS; // Cycle the LED thru all available colors - - SOC = 5000; // 50.00% + SOC = 5000; // 50.00% StateOfHealth = 9900; // 99.00% diff --git a/Software/src/battery/TEST-FAKE-BATTERY.h b/Software/src/battery/TEST-FAKE-BATTERY.h index 22f0a9c2..8fde83ad 100644 --- a/Software/src/battery/TEST-FAKE-BATTERY.h +++ b/Software/src/battery/TEST-FAKE-BATTERY.h @@ -28,7 +28,6 @@ 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 void update_values_test_battery(); void receive_can_test_battery(CAN_frame_t rx_frame); diff --git a/Software/src/devboard/config.h b/Software/src/devboard/config.h index 04f12822..ff4b2f7b 100644 --- a/Software/src/devboard/config.h +++ b/Software/src/devboard/config.h @@ -32,11 +32,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/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 3bf9035b..0cc51e8d 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -1,82 +1,170 @@ #include "events.h" +#ifndef UNIT_TEST +#include +#endif + #include "../../../USER_SETTINGS.h" #include "../config.h" +#include "timer.h" + +#define EE_MAGIC_HEADER_VALUE 0xAA55 +#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; -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]; +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; +} 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); + + uint16_t header = EEPROM.readUShort(EE_EVENT_LOG_START_ADDRESS); + if (header != EE_MAGIC_HEADER_VALUE) { + 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); + 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(); + } + + 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 = false; } - // 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; + 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_WARNING; + 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_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_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_DUMMY_INFO].log = true; + events.entries[EVENT_DUMMY_DEBUG].log = true; + events.entries[EVENT_DUMMY_WARNING].log = true; + events.entries[EVENT_DUMMY_ERROR].log = true; + + events.second_timer.set_interval(1000); + events.ee_timer.set_interval(EE_WRITE_PERIOD_MINUTES * 60 * 1000); // Write to EEPROM every X minutes } 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); } -/* 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"; +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(); } } -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: @@ -107,22 +195,167 @@ 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!"; 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 the event name but skip "EVENT_" that should always be first + return EVENTS_ENUM_TYPE_STRING[event] + 6; +} + +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: + bms_status = ACTIVE; + break; + case EVENT_LEVEL_UPDATE: + bms_status = UPDATING; + break; + case EVENT_LEVEL_ERROR: + 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++; } - return fullString; } -static void set_event_message(EVENTS_ENUM_TYPE event) { - const char* message = get_event_message(event); - snprintf(event_message, sizeof(event_message), "%s", message); +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)); +} + +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); + 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 + if (events.ee_timer.elapsed()) { + EEPROM.commit(); + } } diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index e67824df..ddd32227 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -4,13 +4,26 @@ #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) \ @@ -25,31 +38,63 @@ extern uint32_t time_seconds; XX(EVENT_CELL_OVER_VOLTAGE) \ XX(EVENT_CELL_DEVIATION_HIGH) \ XX(EVENT_UNKNOWN_EVENT_SET) \ - XX(EVENT_DUMMY) \ + XX(EVENT_OTA_UPDATE) \ + 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_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) -const char* get_event_enum_string(EVENTS_ENUM_TYPE event); +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; -const char* get_event_message(EVENTS_ENUM_TYPE event); +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_led_color_display_text(u_int8_t led_color); +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); + +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 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..0af89304 --- /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("bms_status: "); + Serial.println(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("bms_status: "); + Serial.println(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("bms_status: "); + Serial.println(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("bms_status: "); + Serial.println(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("bms_status: "); + Serial.println(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("bms_status: "); + Serial.println(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("bms_status: "); + Serial.println(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("bms_status: "); + Serial.println(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("bms_status: "); + Serial.println(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("bms_status: "); + Serial.println(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("bms_status: "); + Serial.println(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/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/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 16bd556b..c2eab57a 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -1,5 +1,6 @@ #include "webserver.h" #include +#include "../utils/events.h" // Create AsyncWebServer object on port 80 AsyncWebServer server(80); @@ -507,8 +508,10 @@ String processor(const String& var) { content += "

Cell min: " + String(cell_min_voltage) + " mV

"; content += "

Temperature max: " + String(tempMaxFloat, 1) + " C

"; content += "

Temperature min: " + String(tempMinFloat, 1) + " C

"; - if (bms_status == 3) { + if (bms_status == ACTIVE) { content += "

BMS Status: OK

"; + } else if (bms_status == UPDATING) { + content += "

BMS Status: UPDATING

"; } else { content += "

BMS Status: FAULT

"; } @@ -630,15 +633,11 @@ String processor(const String& var) { void onOTAStart() { // Log when OTA has started - Serial.println("OTA update started!"); ESP32Can.CANStop(); - bms_status = UPDATING; //Inform inverter that we are updating - LEDcolor = BLUE; + set_event(EVENT_OTA_UPDATE, 0); } void onOTAProgress(size_t current, size_t final) { - bms_status = UPDATING; //Inform inverter that we are updating - LEDcolor = BLUE; // Log every 1 second if (millis() - ota_progress_millis > 1000) { ota_progress_millis = millis(); @@ -653,8 +652,7 @@ void onOTAEnd(bool success) { } else { Serial.println("There was an error during OTA update!"); } - bms_status = UPDATING; //Inform inverter that we are updating - LEDcolor = BLUE; + clear_event(EVENT_OTA_UPDATE); } template // This function makes power values appear as W when under 1000, and kW when over diff --git a/Software/src/inverter/BYD-CAN.h b/Software/src/inverter/BYD-CAN.h index 2539fefe..4a292c45 100644 --- a/Software/src/inverter/BYD-CAN.h +++ b/Software/src/inverter/BYD-CAN.h @@ -13,7 +13,6 @@ 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; diff --git a/Software/src/inverter/LUNA2000-MODBUS.h b/Software/src/inverter/LUNA2000-MODBUS.h index 6d4d851a..93287410 100644 --- a/Software/src/inverter/LUNA2000-MODBUS.h +++ b/Software/src/inverter/LUNA2000-MODBUS.h @@ -14,7 +14,6 @@ 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; diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp index 119398f2..f4d64bb6 100644 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp +++ b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.cpp @@ -1,6 +1,7 @@ //SERIAL-LINK-TRANSMITTER-INVERTER.cpp #include "SERIAL-LINK-TRANSMITTER-INVERTER.h" +#include "../devboard/utils/events.h" /* * SerialDataLink @@ -107,10 +108,9 @@ void manageSerialLinkTransmitter() { Serial.println("SerialDataLink : max_target_discharge_power = 0"); Serial.println("SerialDataLink : max_target_charge_power = 0"); - bms_status = 4; //FAULT max_target_discharge_power = 0; max_target_charge_power = 0; - LEDcolor = RED; + set_event(EVENT_SERIAL_TX_FAILURE, 0); // throw error } /* diff --git a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h index 66143af0..613190ce 100644 --- a/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h +++ b/Software/src/inverter/SERIAL-LINK-TRANSMITTER-INVERTER.h @@ -24,7 +24,6 @@ extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 funct extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t cell_max_voltage; //mV, 0-4350 extern uint16_t cell_min_voltage; //mV, 0-4350 -extern uint8_t LEDcolor; //Enum, 0-10 extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false extern bool LFP_Chemistry; diff --git a/Software/src/inverter/SMA-CAN.h b/Software/src/inverter/SMA-CAN.h index ca4cc940..ad8fa09d 100644 --- a/Software/src/inverter/SMA-CAN.h +++ b/Software/src/inverter/SMA-CAN.h @@ -13,7 +13,6 @@ 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) @@ -22,7 +21,6 @@ extern uint16_t cell_max_voltage; //mV, 0-4350 extern uint16_t cell_min_voltage; //mV, 0-4350 extern uint16_t min_voltage; extern uint16_t max_voltage; -extern uint8_t LEDcolor; //Enum, 0-10 extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false diff --git a/Software/src/inverter/SOFAR-CAN.h b/Software/src/inverter/SOFAR-CAN.h index 1a28ba6d..75d69602 100644 --- a/Software/src/inverter/SOFAR-CAN.h +++ b/Software/src/inverter/SOFAR-CAN.h @@ -14,14 +14,12 @@ extern uint16_t capacity_Wh; //Wh, 0-60000 extern uint16_t remaining_capacity_Wh; //Wh, 0-60000 extern uint16_t max_target_discharge_power; //W, 0-60000 extern uint16_t max_target_charge_power; //W, 0-60000 -extern uint8_t bms_status; //Enum, 0-5 extern uint8_t bms_char_dis_status; //Enum, 0-2 extern uint16_t stat_batt_power; //W, Goes thru convert2unsignedint16 function (5W = 5, -5W = 65530) extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t temperature_max; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385) extern uint16_t cell_max_voltage; //mV, 0-4350 extern uint16_t cell_min_voltage; //mV, 0-4350 -extern uint8_t LEDcolor; //Enum, 0-10 extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false diff --git a/Software/src/inverter/SOLAX-CAN.h b/Software/src/inverter/SOLAX-CAN.h index 8460fd07..c366ef3a 100644 --- a/Software/src/inverter/SOLAX-CAN.h +++ b/Software/src/inverter/SOLAX-CAN.h @@ -16,7 +16,6 @@ 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; diff --git a/Software/src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.cpp b/Software/src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.cpp index 523358f0..a6a02142 100644 --- a/Software/src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.cpp +++ b/Software/src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.cpp @@ -1,5 +1,6 @@ #include "ESP32CAN.h" #include +#include "../../devboard/utils/events.h" int ESP32CAN::CANInit() { return CAN_init(); @@ -12,13 +13,14 @@ int ESP32CAN::CANWriteFrame(const CAN_frame_t* p_frame) { tx_ok = (result == 0) ? true : false; if (tx_ok == false) { Serial.println("CAN failure! Check wires"); - LEDcolor = 3; + set_event(EVENT_CAN_TX_FAILURE, 0); start_time = millis(); + } else { + clear_event(EVENT_CAN_TX_FAILURE); } } else { if ((millis() - start_time) >= 2000) { tx_ok = true; - LEDcolor = 0; } } return result; diff --git a/Software/src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h b/Software/src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h index e2e2251c..fcca661d 100644 --- a/Software/src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h +++ b/Software/src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h @@ -3,7 +3,6 @@ #include "../../lib/miwagner-ESP32-Arduino-CAN/CAN.h" #include "../../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" -extern uint8_t LEDcolor; class ESP32CAN { public: diff --git a/cmake_clean.bat b/cmake_clean.bat new file mode 100644 index 00000000..db5215ae --- /dev/null +++ b/cmake_clean.bat @@ -0,0 +1,15 @@ +@echo off +echo Cleaning up +rmdir /Q/S build +echo Creating new CMake build folder +mkdir build +cd build +echo Building CMake project +call cmake .. +call cmake --build . +echo Executing tests +for %%i in ("test\Debug\*.exe") do ( + echo Running %%i + %%i +) +cd.. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..76cde93f --- /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 ${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..8814cd18 --- /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} %zu 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..3c89db7d --- /dev/null +++ b/test/test_lib.cpp @@ -0,0 +1,13 @@ +#include "test_lib.h" + +#include + +#include "../Software/src/devboard/config.h" + +MySerial Serial; + +unsigned long testlib_millis = 0; + +uint8_t bms_status = ACTIVE; + +uint8_t LEDcolor = GREEN; diff --git a/test/test_lib.h b/test/test_lib.h new file mode 100644 index 00000000..7bde85d7 --- /dev/null +++ b/test/test_lib.h @@ -0,0 +1,48 @@ +#ifndef __TEST_LIB_H__ +#define __TEST_LIB_H__ + +#include +#include +#include +#include + +#include "config.h" +#include "microtest.h" + +using namespace std; + +class MySerial; + +extern unsigned long testlib_millis; +extern MySerial Serial; +extern uint8_t bms_status; +extern uint8_t LEDcolor; + +/* Mock millis() */ +static inline unsigned long millis(void) { + return testlib_millis; +} + +/* Mock Serial class */ +class MySerial { + public: + size_t println(const char* s) { + return print(s, true); // Call print with newline argument true + } + + size_t print(const char* s) { + return print(s, false); // Call print with newline argument false + } + + private: + size_t print(const char* s, bool newline) { + size_t length = printf("%s", s); // Print the string without newline + if (newline) { + printf("\n"); // Add a newline if specified + length++; // Increment length to account for the added newline character + } + return length; // Return the total length printed + } +}; + +#endif diff --git a/test/utils/events_test.cpp_ b/test/utils/events_test.cpp_ new file mode 100644 index 00000000..d7e9771d --- /dev/null +++ b/test/utils/events_test.cpp_ @@ -0,0 +1,105 @@ +// The test library must be included first! +#include "../test_lib.h" + +#include "../../Software/src/devboard/config.h" +#include "../../Software/src/devboard/utils/timer.cpp" + + +class EEPROMClass { + public: + void begin(int s) {} + void writeUShort(int a, uint16_t d) {} + void commit(void) {} + uint16_t readUShort(int a) {} + + template + void get(int address, T &t) {} + + template + void put(int address, const T &t) {} +}; + +EEPROMClass EEPROM; + +#include "../../Software/src/devboard/utils/events.cpp" +/* Local test variables */ +bool elapsed = false; + +/* Stubs */ +void run_sequence_on_target(void) {} + +/* Helper functions */ +/* Test functions */ + +TEST(init_events_test) { + init_events(); + + for (uint8_t i = 0; i < EVENT_NOF_EVENTS; i++) { + ASSERT_EQ(events.entries[i].occurences, 0); + ASSERT_EQ(events.entries[i].data, 0); + ASSERT_EQ(events.entries[i].timestamp, 0); + } +} + +TEST(update_event_time_test) { + // Reset + testlib_millis = 0; + events.time_seconds = 0; + init_events(); + + // No delta, so time shouldn't increase + update_event_time(); + ASSERT_EQ(events.time_seconds, 0); + + // Almost time to bump the seconds + testlib_millis = 999; + update_event_time(); + ASSERT_EQ(events.time_seconds, 0); + + // millis == 1000, so we should add a second + testlib_millis = 1000; + update_event_time(); + ASSERT_EQ(events.time_seconds, 1); + + // We shouldn't add more seconds until 2000 now + testlib_millis = 1999; + update_event_time(); + ASSERT_EQ(events.time_seconds, 1); + testlib_millis = 2000; + update_event_time(); + ASSERT_EQ(events.time_seconds, 2); +} + +TEST(set_event_test) { + // Reset + init_events(); + events.time_seconds = 0; + + // Initially, the event should not have any data or occurences + ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].data, 0); + ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].occurences, 0); + ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 0); + // Set current time and overvoltage event for cell 23 (RED color, bms_status == FAULT) + events.time_seconds = 345; + set_event(EVENT_CELL_OVER_VOLTAGE, 123); + // Ensure proper event data + ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].data, 123); + ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].occurences, 1); + ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 345); + ASSERT_EQ(bms_status, FAULT); +} + +TEST(events_message_test) { + set_event(EVENT_DUMMY_ERROR, 0); // Set dummy event with no data + + ASSERT_STREQ("The dummy error event was set!", get_event_message_string(EVENT_DUMMY_ERROR)); +} + +TEST(events_level_test) { + init_events(); + set_event(EVENT_DUMMY_ERROR, 0); // Set dummy event with no data + + ASSERT_STREQ("ERROR", get_event_level_string(EVENT_DUMMY_ERROR)); +} + +TEST_MAIN();