mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-05 10:49:42 +02:00
Merge pull request #170 from dalathegreat/feature/event-log
Event handling! Unit tests!
This commit is contained in:
commit
8f61883a7e
50 changed files with 1069 additions and 314 deletions
30
.github/workflows/unit-tests.yml
vendored
Normal file
30
.github/workflows/unit-tests.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
name: Run Unit Tests
|
||||
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure and build with CMake
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build .
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
set -e # Exit immediately on non-zero exit code
|
||||
cd build/test
|
||||
dir -s
|
||||
for test_executable in *; do
|
||||
if [ -f "$test_executable" ] && [ -x "$test_executable" ]; then
|
||||
./"$test_executable"
|
||||
fi
|
||||
done
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,5 +1,8 @@
|
|||
# Ignore any .vscode folder
|
||||
*.vscode/
|
||||
|
||||
# Ignore any files in the build folder
|
||||
Software/build/
|
||||
# Ignore any files in any build folder
|
||||
*build/
|
||||
|
||||
# Ignore .exe (unit tests)
|
||||
*.exe
|
10
CMakeLists.txt
Normal file
10
CMakeLists.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Set the C++ standard to C++20
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
project(BatteryEmulator)
|
||||
|
||||
# add_subdirectory(Software/src/devboard/utils)
|
||||
add_subdirectory(test)
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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--;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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--;
|
||||
}
|
||||
|
|
|
@ -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--;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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--;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// SERIAL-LINK-RECEIVER-FROM-BATTERY.cpp
|
||||
|
||||
#include "SERIAL-LINK-RECEIVER-FROM-BATTERY.h"
|
||||
#include <Arduino.h>
|
||||
#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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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%
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,82 +1,170 @@
|
|||
#include "events.h"
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
#include <EEPROM.h>
|
||||
#endif
|
||||
|
||||
#include "../../../USER_SETTINGS.h"
|
||||
#include "../config.h"
|
||||
#include "timer.h"
|
||||
|
||||
unsigned long previous_millis = 0;
|
||||
uint32_t time_seconds = 0;
|
||||
static uint8_t total_led_color = GREEN;
|
||||
static char event_message[256];
|
||||
EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
|
||||
#define EE_MAGIC_HEADER_VALUE 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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// YELLOW warning events below
|
||||
entries[EVENT_12V_LOW].led_color = YELLOW;
|
||||
entries[EVENT_CAN_WARNING].led_color = YELLOW;
|
||||
entries[EVENT_CELL_DEVIATION_HIGH].led_color = YELLOW;
|
||||
entries[EVENT_KWH_PLAUSIBILITY_ERROR].led_color = YELLOW;
|
||||
for (uint16_t i = 0; i < EVENT_NOF_EVENTS; i++) {
|
||||
events.entries[i].data = 0;
|
||||
events.entries[i].timestamp = 0;
|
||||
events.entries[i].occurences = 0;
|
||||
events.entries[i].log = false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void clear_event(EVENTS_ENUM_TYPE event) {
|
||||
if (events.entries[event].state == EVENT_STATE_ACTIVE) {
|
||||
events.entries[event].state = EVENT_STATE_INACTIVE;
|
||||
update_event_level();
|
||||
update_bms_status();
|
||||
}
|
||||
}
|
||||
|
||||
/* Local functions */
|
||||
static void update_led_color(EVENTS_ENUM_TYPE event) {
|
||||
total_led_color = (total_led_color == RED) ? RED : entries[event].led_color;
|
||||
}
|
||||
|
||||
const char* get_led_color_display_text(u_int8_t led_color) {
|
||||
switch (led_color) {
|
||||
case RED:
|
||||
return "Error";
|
||||
case YELLOW:
|
||||
return "Warning";
|
||||
case GREEN:
|
||||
return "Info";
|
||||
case BLUE:
|
||||
return "Debug";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
const char* get_event_message(EVENTS_ENUM_TYPE event) {
|
||||
const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
||||
switch (event) {
|
||||
case EVENT_CAN_FAILURE:
|
||||
case EVENT_CAN_RX_FAILURE:
|
||||
return "No CAN communication detected for 60s. Shutting down battery control.";
|
||||
case EVENT_CAN_WARNING:
|
||||
case EVENT_CAN_RX_WARNING:
|
||||
return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!";
|
||||
case EVENT_CAN_TX_FAILURE:
|
||||
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
|
||||
case EVENT_WATER_INGRESS:
|
||||
return "Water leakage inside battery detected. Operation halted. Inspect battery!";
|
||||
case EVENT_12V_LOW:
|
||||
|
@ -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 fullString;
|
||||
// Return the event name but skip "EVENT_" that should always be first
|
||||
return EVENTS_ENUM_TYPE_STRING[event] + 6;
|
||||
}
|
||||
|
||||
static void set_event_message(EVENTS_ENUM_TYPE event) {
|
||||
const char* message = get_event_message(event);
|
||||
snprintf(event_message, sizeof(event_message), "%s", message);
|
||||
const char* get_event_level_string(EVENTS_ENUM_TYPE event) {
|
||||
// Return the event level but skip "EVENT_LEVEL_" that should always be first
|
||||
return EVENTS_LEVEL_TYPE_STRING[events.entries[event].level] + 12;
|
||||
}
|
||||
|
||||
const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event) {
|
||||
return &events.entries[event];
|
||||
}
|
||||
|
||||
EVENTS_LEVEL_TYPE get_event_level(void) {
|
||||
return events.level;
|
||||
}
|
||||
|
||||
/* Local functions */
|
||||
|
||||
static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) {
|
||||
// Just some defensive stuff if someone sets an unknown event
|
||||
if (event >= EVENT_NOF_EVENTS) {
|
||||
event = EVENT_UNKNOWN_EVENT_SET;
|
||||
}
|
||||
|
||||
// If the event is already set, no reason to continue
|
||||
if ((events.entries[event].state != EVENT_STATE_ACTIVE) &&
|
||||
(events.entries[event].state != EVENT_STATE_ACTIVE_LATCHED)) {
|
||||
events.entries[event].occurences++;
|
||||
if (events.entries[event].log) {
|
||||
log_event(event, data);
|
||||
}
|
||||
}
|
||||
|
||||
// We should set the event, update event info
|
||||
events.entries[event].timestamp = events.time_seconds;
|
||||
events.entries[event].data = data;
|
||||
// Check if the event is latching
|
||||
events.entries[event].state = latched ? EVENT_STATE_ACTIVE_LATCHED : EVENT_STATE_ACTIVE;
|
||||
|
||||
update_event_level();
|
||||
update_bms_status();
|
||||
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println(get_event_message_string(event));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void update_bms_status(void) {
|
||||
switch (events.level) {
|
||||
case EVENT_LEVEL_INFO:
|
||||
case EVENT_LEVEL_WARNING:
|
||||
case EVENT_LEVEL_DEBUG:
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,26 @@
|
|||
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
extern unsigned long previous_millis;
|
||||
extern uint32_t time_seconds;
|
||||
#endif
|
||||
|
||||
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
||||
|
||||
#define GENERATE_ENUM(ENUM) ENUM,
|
||||
#define GENERATE_STRING(STRING) #STRING,
|
||||
|
||||
/** EVENT ENUMERATION
|
||||
*
|
||||
* Do not change the order!
|
||||
* When adding events, add them RIGHT BEFORE the EVENT_NOF_EVENTS enum.
|
||||
* In addition, the event name must start with "EVENT_"
|
||||
*
|
||||
* After adding an event, assign the proper event level in events.cpp:init_events()
|
||||
*/
|
||||
|
||||
#define EVENTS_ENUM_TYPE(XX) \
|
||||
XX(EVENT_CAN_FAILURE) \
|
||||
XX(EVENT_CAN_WARNING) \
|
||||
XX(EVENT_CAN_RX_FAILURE) \
|
||||
XX(EVENT_CAN_RX_WARNING) \
|
||||
XX(EVENT_CAN_TX_FAILURE) \
|
||||
XX(EVENT_WATER_INGRESS) \
|
||||
XX(EVENT_12V_LOW) \
|
||||
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
|
||||
|
@ -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)
|
||||
|
||||
typedef enum { EVENTS_LEVEL_TYPE(GENERATE_ENUM) } EVENTS_LEVEL_TYPE;
|
||||
|
||||
typedef enum {
|
||||
EVENT_STATE_PENDING = 0,
|
||||
EVENT_STATE_INACTIVE,
|
||||
EVENT_STATE_ACTIVE,
|
||||
EVENT_STATE_ACTIVE_LATCHED
|
||||
} EVENTS_STATE_TYPE;
|
||||
|
||||
typedef struct {
|
||||
uint32_t timestamp; // Time in seconds since startup when the event occurred
|
||||
uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage
|
||||
uint8_t occurences; // Number of occurrences since startup
|
||||
EVENTS_LEVEL_TYPE level; // Event level, i.e. ERROR/WARNING...
|
||||
EVENTS_STATE_TYPE state; // Event state, i.e. ACTIVE/INACTIVE...
|
||||
bool log;
|
||||
} EVENTS_STRUCT_TYPE;
|
||||
|
||||
const char* get_event_enum_string(EVENTS_ENUM_TYPE event);
|
||||
const char* get_event_message_string(EVENTS_ENUM_TYPE event);
|
||||
const char* get_event_level_string(EVENTS_ENUM_TYPE event);
|
||||
const char* get_event_type(EVENTS_ENUM_TYPE event);
|
||||
|
||||
const char* get_event_message(EVENTS_ENUM_TYPE event);
|
||||
|
||||
const char* get_led_color_display_text(u_int8_t led_color);
|
||||
EVENTS_LEVEL_TYPE get_event_level(void);
|
||||
|
||||
void init_events(void);
|
||||
void set_event_latched(EVENTS_ENUM_TYPE event, uint8_t data);
|
||||
void set_event(EVENTS_ENUM_TYPE event, uint8_t data);
|
||||
void update_event_timestamps(void);
|
||||
typedef struct {
|
||||
uint32_t timestamp; // Time in seconds since startup when the event occurred
|
||||
uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage
|
||||
uint8_t occurences; // Number of occurrences since startup
|
||||
uint8_t led_color; // Weirdly indented comment
|
||||
} EVENTS_STRUCT_TYPE;
|
||||
extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
|
||||
void clear_event(EVENTS_ENUM_TYPE event);
|
||||
|
||||
const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event);
|
||||
|
||||
void run_event_handling(void);
|
||||
|
||||
void run_sequence_on_target(void);
|
||||
|
||||
extern uint8_t bms_status; //Enum, 0-5
|
||||
|
||||
#endif // __MYTIMER_H__
|
||||
|
|
142
Software/src/devboard/utils/events_test_on_target.cpp
Normal file
142
Software/src/devboard/utils/events_test_on_target.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
#include "events.h"
|
||||
#include "timer.h"
|
||||
|
||||
typedef enum {
|
||||
ETOT_INIT,
|
||||
ETOT_FIRST_WAIT,
|
||||
ETOT_INFO,
|
||||
ETOT_INFO_CLEAR,
|
||||
ETOT_DEBUG,
|
||||
ETOT_DEBUG_CLEAR,
|
||||
ETOT_WARNING,
|
||||
ETOT_WARNING_CLEAR,
|
||||
ETOT_ERROR,
|
||||
ETOT_ERROR_CLEAR,
|
||||
ETOT_ERROR_LATCHED,
|
||||
ETOT_DONE
|
||||
} ETOT_TYPE;
|
||||
|
||||
MyTimer timer(5000);
|
||||
ETOT_TYPE events_test_state = ETOT_INIT;
|
||||
|
||||
void run_sequence_on_target(void) {
|
||||
#ifdef INCLUDE_EVENTS_TEST
|
||||
switch (events_test_state) {
|
||||
case ETOT_INIT:
|
||||
timer.set_interval(10000);
|
||||
events_test_state = ETOT_FIRST_WAIT;
|
||||
Serial.println("Events test: initialized");
|
||||
Serial.print("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
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "events_html.h"
|
||||
#include <Arduino.h>
|
||||
#include "../utils/events.h"
|
||||
|
||||
const char EVENTS_HTML_START[] = R"=====(
|
||||
<style>body{background-color:#000;color:#fff}.event-log{display:flex;flex-direction:column}.event{display:flex;flex-wrap:wrap;border:1px solid #fff;padding:10px}.event>div{flex:1;min-width:100px;max-width:90%;word-break:break-word}</style><div style="background-color:#303e47;padding:10px;margin-bottom:10px;border-radius:25px"><div class="event-log"><div class="event" style="background-color:#1e2c33;font-weight:700"><div>Event Type</div><div>Severity</div><div>Last Event</div><div>Count</div><div>Data</div><div>Message</div></div>
|
||||
|
@ -17,18 +18,22 @@ String events_processor(const String& var) {
|
|||
content.reserve(5000);
|
||||
// Page format
|
||||
content.concat(FPSTR(EVENTS_HTML_START));
|
||||
const EVENTS_STRUCT_TYPE* event_pointer;
|
||||
for (int i = 0; i < EVENT_NOF_EVENTS; i++) {
|
||||
Serial.println("Event: " + String(get_event_enum_string(static_cast<EVENTS_ENUM_TYPE>(i))) +
|
||||
" count: " + String(entries[i].occurences) + " seconds: " + String(entries[i].timestamp) +
|
||||
" data: " + String(entries[i].data));
|
||||
if (entries[i].occurences > 0) {
|
||||
event_pointer = get_event_pointer((EVENTS_ENUM_TYPE)i);
|
||||
EVENTS_ENUM_TYPE event_handle = static_cast<EVENTS_ENUM_TYPE>(i);
|
||||
Serial.println("Event: " + String(get_event_enum_string(event_handle)) +
|
||||
" count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) +
|
||||
" data: " + String(event_pointer->data) +
|
||||
" level: " + String(get_event_level_string(event_handle)));
|
||||
if (event_pointer->occurences > 0) {
|
||||
content.concat("<div class='event'>");
|
||||
content.concat("<div>" + String(get_event_enum_string(static_cast<EVENTS_ENUM_TYPE>(i))) + "</div>");
|
||||
content.concat("<div>" + String(get_led_color_display_text(entries[i].led_color)) + "</div>");
|
||||
content.concat("<div class='last-event-seconds-ago'>" + String(entries[i].timestamp) + "</div>");
|
||||
content.concat("<div>" + String(entries[i].occurences) + "</div>");
|
||||
content.concat("<div>" + String(entries[i].data) + "</div>");
|
||||
content.concat("<div>" + String(get_event_message(static_cast<EVENTS_ENUM_TYPE>(i))) + "</div>");
|
||||
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
|
||||
content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>");
|
||||
content.concat("<div class='last-event-seconds-ago'>" + String(event_pointer->timestamp) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->occurences) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->data) + "</div>");
|
||||
content.concat("<div>" + String(get_event_message_string(event_handle)) + "</div>");
|
||||
content.concat("<div class='timestamp' style='display:none;'>" + String(millis() / 1000) + "</div>");
|
||||
content.concat("</div>"); // End of event row
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
#ifndef EVENTS_H
|
||||
#define EVENTS_H
|
||||
|
||||
#include "../utils/events.h"
|
||||
|
||||
extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
|
||||
#include <Arduino.h>
|
||||
|
||||
/**
|
||||
* @brief Replaces placeholder with content section in web page
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "webserver.h"
|
||||
#include <Preferences.h>
|
||||
#include "../utils/events.h"
|
||||
|
||||
// Create AsyncWebServer object on port 80
|
||||
AsyncWebServer server(80);
|
||||
|
@ -507,8 +508,10 @@ String processor(const String& var) {
|
|||
content += "<h4>Cell min: " + String(cell_min_voltage) + " mV</h4>";
|
||||
content += "<h4>Temperature max: " + String(tempMaxFloat, 1) + " C</h4>";
|
||||
content += "<h4>Temperature min: " + String(tempMinFloat, 1) + " C</h4>";
|
||||
if (bms_status == 3) {
|
||||
if (bms_status == ACTIVE) {
|
||||
content += "<h4>BMS Status: OK </h4>";
|
||||
} else if (bms_status == UPDATING) {
|
||||
content += "<h4>BMS Status: UPDATING </h4>";
|
||||
} else {
|
||||
content += "<h4>BMS Status: FAULT </h4>";
|
||||
}
|
||||
|
@ -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 <typename T> // This function makes power values appear as W when under 1000, and kW when over
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
/*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "ESP32CAN.h"
|
||||
#include <Arduino.h>
|
||||
#include "../../devboard/utils/events.h"
|
||||
|
||||
int ESP32CAN::CANInit() {
|
||||
return CAN_init();
|
||||
|
@ -12,13 +13,14 @@ int ESP32CAN::CANWriteFrame(const CAN_frame_t* p_frame) {
|
|||
tx_ok = (result == 0) ? true : false;
|
||||
if (tx_ok == false) {
|
||||
Serial.println("CAN failure! Check wires");
|
||||
LEDcolor = 3;
|
||||
set_event(EVENT_CAN_TX_FAILURE, 0);
|
||||
start_time = millis();
|
||||
} else {
|
||||
clear_event(EVENT_CAN_TX_FAILURE);
|
||||
}
|
||||
} else {
|
||||
if ((millis() - start_time) >= 2000) {
|
||||
tx_ok = true;
|
||||
LEDcolor = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "../../lib/miwagner-ESP32-Arduino-CAN/CAN.h"
|
||||
#include "../../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
|
||||
extern uint8_t LEDcolor;
|
||||
|
||||
class ESP32CAN {
|
||||
public:
|
||||
|
|
15
cmake_clean.bat
Normal file
15
cmake_clean.bat
Normal file
|
@ -0,0 +1,15 @@
|
|||
@echo off
|
||||
echo Cleaning up
|
||||
rmdir /Q/S build
|
||||
echo Creating new CMake build folder
|
||||
mkdir build
|
||||
cd build
|
||||
echo Building CMake project
|
||||
call cmake ..
|
||||
call cmake --build .
|
||||
echo Executing tests
|
||||
for %%i in ("test\Debug\*.exe") do (
|
||||
echo Running %%i
|
||||
%%i
|
||||
)
|
||||
cd..
|
18
test/CMakeLists.txt
Normal file
18
test/CMakeLists.txt
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Include the directory with your source files
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Software/src/devboard ${CMAKE_SOURCE_DIR}/Software/src/devboard/utils . )
|
||||
|
||||
# Create a variable to store the list of test files
|
||||
file(GLOB TEST_SOURCES utils/*.cpp)
|
||||
|
||||
# Loop through each test source file and create an executable
|
||||
foreach(TEST_SOURCE ${TEST_SOURCES})
|
||||
# Extract the test name without extension
|
||||
get_filename_component(TEST_NAME ${TEST_SOURCE} NAME_WE)
|
||||
|
||||
# Create an executable for the test
|
||||
add_executable(${TEST_NAME} ${TEST_SOURCE} test_lib.cpp)
|
||||
|
||||
# Apply the target_compile_definitions for the test
|
||||
target_compile_definitions(${TEST_NAME} PRIVATE UNIT_TEST)
|
||||
|
||||
endforeach()
|
209
test/microtest.h
Normal file
209
test/microtest.h
Normal file
|
@ -0,0 +1,209 @@
|
|||
//
|
||||
// microtest.h
|
||||
//
|
||||
// URL: https://github.com/torpedro/microtest.h
|
||||
// Author: Pedro Flemming (http://torpedro.com/)
|
||||
// License: MIT License (https://github.com/torpedro/microtest.h/blob/master/LICENSE)
|
||||
// Copyright (c) 2017 Pedro Flemming
|
||||
//
|
||||
// This is a small header-only C++ unit testing framework.
|
||||
// It allows to define small unit tests with set of assertions available.
|
||||
//
|
||||
#ifndef __MICROTEST_H__
|
||||
#define __MICROTEST_H__
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
////////////////
|
||||
// Assertions //
|
||||
////////////////
|
||||
|
||||
#define ASSERT(cond) ASSERT_TRUE(cond);
|
||||
|
||||
#define ASSERT_TRUE(cond) \
|
||||
if (!(cond)) \
|
||||
throw mt::AssertFailedException(#cond, __FILE__, __LINE__);
|
||||
|
||||
#define ASSERT_FALSE(cond) \
|
||||
if (cond) \
|
||||
throw mt::AssertFailedException(#cond, __FILE__, __LINE__);
|
||||
|
||||
#define ASSERT_NULL(value) ASSERT_TRUE(value == NULL);
|
||||
|
||||
#define ASSERT_NOTNULL(value) ASSERT_TRUE(value != NULL);
|
||||
|
||||
#define ASSERT_STREQ(a, b) \
|
||||
if (std::string(a).compare(std::string(b)) != 0) { \
|
||||
printf("%s{ info} %s", mt::yellow(), mt::def()); \
|
||||
std::cout << "Actual values: " << a << " != " << b << std::endl; \
|
||||
throw mt::AssertFailedException(#a " == " #b, __FILE__, __LINE__); \
|
||||
}
|
||||
|
||||
#define ASSERT_STRNEQ(a, b) \
|
||||
if (std::string(a).compare(std::string(b)) != = 0) { \
|
||||
printf("%s{ info} %s", mt::yellow(), mt::def()); \
|
||||
std::cout << "Actual values: " << a << " == " << b << std::endl; \
|
||||
throw mt::AssertFailedException(#a " != " #b, __FILE__, __LINE__); \
|
||||
}
|
||||
|
||||
#define ASSERT_EQ(a, b) \
|
||||
if (a != b) { \
|
||||
printf("%s{ info} %s", mt::yellow(), mt::def()); \
|
||||
std::cout << "Actual values: " << a << " != " << b << std::endl; \
|
||||
} \
|
||||
ASSERT(a == b);
|
||||
|
||||
#define ASSERT_NEQ(a, b) \
|
||||
if (a == b) { \
|
||||
printf("%s{ info} %s", mt::yellow(), mt::def()); \
|
||||
std::cout << "Actual values: " << a << " == " << b << std::endl; \
|
||||
} \
|
||||
ASSERT(a != b);
|
||||
|
||||
////////////////
|
||||
// Unit Tests //
|
||||
////////////////
|
||||
|
||||
#define TEST(name) \
|
||||
void name(); \
|
||||
namespace { \
|
||||
bool __##name = mt::TestsManager::AddTest(name, #name); \
|
||||
} \
|
||||
void name()
|
||||
|
||||
///////////////
|
||||
// Framework //
|
||||
///////////////
|
||||
|
||||
namespace mt {
|
||||
|
||||
inline const char* red() {
|
||||
return "\033[1;31m";
|
||||
}
|
||||
|
||||
inline const char* green() {
|
||||
return "\033[0;32m";
|
||||
}
|
||||
|
||||
inline const char* yellow() {
|
||||
return "\033[0;33m";
|
||||
}
|
||||
|
||||
inline const char* def() {
|
||||
return "\033[0m";
|
||||
}
|
||||
|
||||
inline void printRunning(const char* message, FILE* file = stdout) {
|
||||
fprintf(file, "%s{ running}%s %s\n", green(), def(), message);
|
||||
}
|
||||
|
||||
inline void printOk(const char* message, FILE* file = stdout) {
|
||||
fprintf(file, "%s{ ok}%s %s\n", green(), def(), message);
|
||||
}
|
||||
|
||||
inline void printFailed(const char* message, FILE* file = stdout) {
|
||||
fprintf(file, "%s{ failed} %s%s\n", red(), message, def());
|
||||
}
|
||||
|
||||
// Exception that is thrown when an assertion fails.
|
||||
class AssertFailedException : public std::exception {
|
||||
public:
|
||||
AssertFailedException(std::string description, std::string filepath, int line)
|
||||
: std::exception(), description_(description), filepath_(filepath), line_(line){};
|
||||
|
||||
virtual const char* what() const throw() { return description_.c_str(); }
|
||||
|
||||
inline const char* getFilepath() { return filepath_.c_str(); }
|
||||
|
||||
inline int getLine() { return line_; }
|
||||
|
||||
protected:
|
||||
std::string description_;
|
||||
std::string filepath_;
|
||||
int line_;
|
||||
};
|
||||
|
||||
class TestsManager {
|
||||
// Note: static initialization fiasco
|
||||
// http://www.parashift.com/c++-faq-lite/static-init-order.html
|
||||
// http://www.parashift.com/c++-faq-lite/static-init-order-on-first-use.html
|
||||
public:
|
||||
struct Test {
|
||||
const char* name;
|
||||
void (*fn)(void);
|
||||
};
|
||||
|
||||
static std::vector<Test>& tests() {
|
||||
static std::vector<Test> tests_;
|
||||
return tests_;
|
||||
}
|
||||
|
||||
// Adds a new test to the current set of tests.
|
||||
// Returns false if a test with the same name already exists.
|
||||
inline static bool AddTest(void (*fn)(void), const char* name) {
|
||||
tests().push_back({name, fn});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Run all tests that are registered.
|
||||
// Returns the number of tests that failed.
|
||||
inline static size_t RunAllTests(FILE* file = stdout) {
|
||||
size_t num_failed = 0;
|
||||
|
||||
for (const Test& test : tests()) {
|
||||
// Run the test.
|
||||
// If an AsserFailedException is thrown, the test has failed.
|
||||
try {
|
||||
printRunning(test.name, file);
|
||||
|
||||
(*test.fn)();
|
||||
|
||||
printOk(test.name, file);
|
||||
|
||||
} catch (AssertFailedException& e) {
|
||||
printFailed(test.name, file);
|
||||
fprintf(file, " %sAssertion failed: %s%s\n", red(), e.what(), def());
|
||||
fprintf(file, " %s%s:%d%s\n", red(), e.getFilepath(), e.getLine(), def());
|
||||
++num_failed;
|
||||
}
|
||||
}
|
||||
|
||||
int return_code = (num_failed > 0) ? 1 : 0;
|
||||
return return_code;
|
||||
}
|
||||
};
|
||||
|
||||
// Class that will capture the arguments passed to the program.
|
||||
class Runtime {
|
||||
public:
|
||||
static const std::vector<std::string>& args(int argc = -1, char** argv = NULL) {
|
||||
static std::vector<std::string> args_;
|
||||
if (argc >= 0) {
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
args_.push_back(argv[i]);
|
||||
}
|
||||
}
|
||||
return args_;
|
||||
}
|
||||
};
|
||||
} // namespace mt
|
||||
|
||||
#define TEST_MAIN() \
|
||||
int main(int argc, char* argv[]) { \
|
||||
mt::Runtime::args(argc, argv); \
|
||||
\
|
||||
size_t num_failed = mt::TestsManager::RunAllTests(stdout); \
|
||||
if (num_failed == 0) { \
|
||||
fprintf(stdout, "%s{ summary} All tests succeeded!%s\n", mt::green(), mt::def()); \
|
||||
return 0; \
|
||||
} else { \
|
||||
double percentage = 100.0 * num_failed / mt::TestsManager::tests().size(); \
|
||||
fprintf(stderr, "%s{ summary} %zu tests failed (%.2f%%)%s\n", mt::red(), num_failed, percentage, mt::def()); \
|
||||
return -1; \
|
||||
} \
|
||||
}
|
||||
|
||||
#endif // __MICROTEST_H__
|
13
test/test_lib.cpp
Normal file
13
test/test_lib.cpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include "test_lib.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../Software/src/devboard/config.h"
|
||||
|
||||
MySerial Serial;
|
||||
|
||||
unsigned long testlib_millis = 0;
|
||||
|
||||
uint8_t bms_status = ACTIVE;
|
||||
|
||||
uint8_t LEDcolor = GREEN;
|
48
test/test_lib.h
Normal file
48
test/test_lib.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
#ifndef __TEST_LIB_H__
|
||||
#define __TEST_LIB_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#include "config.h"
|
||||
#include "microtest.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class MySerial;
|
||||
|
||||
extern unsigned long testlib_millis;
|
||||
extern MySerial Serial;
|
||||
extern uint8_t bms_status;
|
||||
extern uint8_t LEDcolor;
|
||||
|
||||
/* Mock millis() */
|
||||
static inline unsigned long millis(void) {
|
||||
return testlib_millis;
|
||||
}
|
||||
|
||||
/* Mock Serial class */
|
||||
class MySerial {
|
||||
public:
|
||||
size_t println(const char* s) {
|
||||
return print(s, true); // Call print with newline argument true
|
||||
}
|
||||
|
||||
size_t print(const char* s) {
|
||||
return print(s, false); // Call print with newline argument false
|
||||
}
|
||||
|
||||
private:
|
||||
size_t print(const char* s, bool newline) {
|
||||
size_t length = printf("%s", s); // Print the string without newline
|
||||
if (newline) {
|
||||
printf("\n"); // Add a newline if specified
|
||||
length++; // Increment length to account for the added newline character
|
||||
}
|
||||
return length; // Return the total length printed
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
105
test/utils/events_test.cpp_
Normal file
105
test/utils/events_test.cpp_
Normal file
|
@ -0,0 +1,105 @@
|
|||
// The test library must be included first!
|
||||
#include "../test_lib.h"
|
||||
|
||||
#include "../../Software/src/devboard/config.h"
|
||||
#include "../../Software/src/devboard/utils/timer.cpp"
|
||||
|
||||
|
||||
class EEPROMClass {
|
||||
public:
|
||||
void begin(int s) {}
|
||||
void writeUShort(int a, uint16_t d) {}
|
||||
void commit(void) {}
|
||||
uint16_t readUShort(int a) {}
|
||||
|
||||
template<typename T>
|
||||
void get(int address, T &t) {}
|
||||
|
||||
template<typename T>
|
||||
void put(int address, const T &t) {}
|
||||
};
|
||||
|
||||
EEPROMClass EEPROM;
|
||||
|
||||
#include "../../Software/src/devboard/utils/events.cpp"
|
||||
/* Local test variables */
|
||||
bool elapsed = false;
|
||||
|
||||
/* Stubs */
|
||||
void run_sequence_on_target(void) {}
|
||||
|
||||
/* Helper functions */
|
||||
/* Test functions */
|
||||
|
||||
TEST(init_events_test) {
|
||||
init_events();
|
||||
|
||||
for (uint8_t i = 0; i < EVENT_NOF_EVENTS; i++) {
|
||||
ASSERT_EQ(events.entries[i].occurences, 0);
|
||||
ASSERT_EQ(events.entries[i].data, 0);
|
||||
ASSERT_EQ(events.entries[i].timestamp, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(update_event_time_test) {
|
||||
// Reset
|
||||
testlib_millis = 0;
|
||||
events.time_seconds = 0;
|
||||
init_events();
|
||||
|
||||
// No delta, so time shouldn't increase
|
||||
update_event_time();
|
||||
ASSERT_EQ(events.time_seconds, 0);
|
||||
|
||||
// Almost time to bump the seconds
|
||||
testlib_millis = 999;
|
||||
update_event_time();
|
||||
ASSERT_EQ(events.time_seconds, 0);
|
||||
|
||||
// millis == 1000, so we should add a second
|
||||
testlib_millis = 1000;
|
||||
update_event_time();
|
||||
ASSERT_EQ(events.time_seconds, 1);
|
||||
|
||||
// We shouldn't add more seconds until 2000 now
|
||||
testlib_millis = 1999;
|
||||
update_event_time();
|
||||
ASSERT_EQ(events.time_seconds, 1);
|
||||
testlib_millis = 2000;
|
||||
update_event_time();
|
||||
ASSERT_EQ(events.time_seconds, 2);
|
||||
}
|
||||
|
||||
TEST(set_event_test) {
|
||||
// Reset
|
||||
init_events();
|
||||
events.time_seconds = 0;
|
||||
|
||||
// Initially, the event should not have any data or occurences
|
||||
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].data, 0);
|
||||
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].occurences, 0);
|
||||
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 0);
|
||||
// Set current time and overvoltage event for cell 23 (RED color, bms_status == FAULT)
|
||||
events.time_seconds = 345;
|
||||
set_event(EVENT_CELL_OVER_VOLTAGE, 123);
|
||||
// Ensure proper event data
|
||||
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].data, 123);
|
||||
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].occurences, 1);
|
||||
ASSERT_EQ(events.entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 345);
|
||||
ASSERT_EQ(bms_status, FAULT);
|
||||
}
|
||||
|
||||
TEST(events_message_test) {
|
||||
set_event(EVENT_DUMMY_ERROR, 0); // Set dummy event with no data
|
||||
|
||||
ASSERT_STREQ("The dummy error event was set!", get_event_message_string(EVENT_DUMMY_ERROR));
|
||||
}
|
||||
|
||||
TEST(events_level_test) {
|
||||
init_events();
|
||||
set_event(EVENT_DUMMY_ERROR, 0); // Set dummy event with no data
|
||||
|
||||
ASSERT_STREQ("ERROR", get_event_level_string(EVENT_DUMMY_ERROR));
|
||||
}
|
||||
|
||||
TEST_MAIN();
|
Loading…
Add table
Add a link
Reference in a new issue