From 4d95fcfc31559e1777cac3357fcff13816b6a16e Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 01:19:37 +0100 Subject: [PATCH 01/44] First commit --- Software/Software.ino | 5 ++++ Software/src/devboard/utils/events.cpp | 33 ++++++++++++++++++++++++++ Software/src/devboard/utils/events.h | 27 +++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 Software/src/devboard/utils/events.cpp create mode 100644 Software/src/devboard/utils/events.h diff --git a/Software/Software.ino b/Software/Software.ino index 2ec566d6..eb65591a 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -8,6 +8,7 @@ #include "src/battery/BATTERIES.h" #include "src/charger/CHARGERS.h" #include "src/devboard/config.h" +#include "src/devboard/utils/events.h" #include "src/inverter/INVERTERS.h" #include "src/lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h" #include "src/lib/eModbus-eModbus/Logging.h" @@ -129,6 +130,8 @@ void setup() { init_webserver(); #endif + init_events(); + init_CAN(); init_LED(); @@ -183,6 +186,7 @@ void loop() { { previousMillisUpdateVal = millis(); update_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus. + set_event(EVENT_DUMMY, (uint8_t)millis()); } // Output @@ -190,6 +194,7 @@ void loop() { #ifdef DUAL_CAN send_can2(); #endif + update_event_timestamps(); } // Initialization functions diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp new file mode 100644 index 00000000..b888f9e9 --- /dev/null +++ b/Software/src/devboard/utils/events.cpp @@ -0,0 +1,33 @@ +#include "events.h" + +typedef struct { + uint32_t timestamp; // Time in seconds since startup when the event occurred + uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage + uint8_t occurences; // Number of occurrences since startup +} EVENTS_STRUCT_TYPE; + +static EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; +static unsigned long previous_millis = 0; +static uint32_t time_seconds = 0; + +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; + } +} + +void set_event(EVENTS_ENUM_TYPE event, uint8_t data) { + entries[event].timestamp = time_seconds; + entries[event].data = data; + entries[event].occurences++; +} + +void update_event_timestamps(void) { + unsigned long new_millis = millis(); + if (new_millis - previous_millis >= 1000) { + time_seconds++; + previous_millis = new_millis; + } +} diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h new file mode 100644 index 00000000..a23fa608 --- /dev/null +++ b/Software/src/devboard/utils/events.h @@ -0,0 +1,27 @@ +#ifndef __EVENTS_H__ +#define __EVENTS_H__ + +#include + +typedef enum { + EVENT_CAN_FAILURE = 0u, + EVENT_CAN_WARNING, + EVENT_WATER_INGRESS, + EVENT_12V_LOW, + EVENT_SOC_PLAUSIBILITY_ERROR, + EVENT_KWH_PLAUSIBILITY_ERROR, + EVENT_BATTERY_CHG_DISCHG_STOP_REQ, + EVENT_LOW_SOH, + EVENT_HVIL_FAILURE, + EVENT_INTERNAL_OPEN_FAULT, + EVENT_CELL_UNDER_VOLTAGE, + EVENT_CELL_OVER_VOLTAGE, + EVENT_DUMMY, + EVENT_NOF_EVENTS +} EVENTS_ENUM_TYPE; + +void init_events(void); +void set_event(EVENTS_ENUM_TYPE event, uint8_t data); +void update_event_timestamps(void); + +#endif // __MYTIMER_H__ From 44c02745bf941637f0ef3bbb006e5064550533b2 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:10:12 +0100 Subject: [PATCH 02/44] Improvements and unit tests --- .gitignore | 2 + Software/USER_SETTINGS.h | 4 +- Software/src/devboard/utils/events.cpp | 93 ++++++++ Software/src/devboard/utils/events.h | 6 + Software/src/devboard/utils/events_test.cpp | 65 ++++++ Software/src/devboard/utils/test_commands.txt | 1 + Software/test/microtest.h | 209 ++++++++++++++++++ Software/test/test_lib.cpp | 6 + Software/test/test_lib.h | 41 ++++ 9 files changed, 425 insertions(+), 2 deletions(-) create mode 100644 Software/src/devboard/utils/events_test.cpp create mode 100644 Software/src/devboard/utils/test_commands.txt create mode 100644 Software/test/microtest.h create mode 100644 Software/test/test_lib.cpp create mode 100644 Software/test/test_lib.h diff --git a/.gitignore b/.gitignore index 65ad4290..ea25fe55 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ # Ignore any files in the build folder Software/build/ + +*.exe \ No newline at end of file diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 0b4fe1c1..115a10a8 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -12,7 +12,7 @@ //#define CHADEMO_BATTERY //#define IMIEV_CZERO_ION_BATTERY //#define KIA_HYUNDAI_64_BATTERY -//#define NISSAN_LEAF_BATTERY +#define NISSAN_LEAF_BATTERY //#define RENAULT_KANGOO_BATTERY //#define RENAULT_ZOE_BATTERY //#define SANTA_FE_PHEV_BATTERY @@ -21,7 +21,7 @@ /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus -//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU +#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU //#define LUNA2000_MODBUS //Enable this line to emulate a "Luna2000 battery" over Modbus RTU //#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index b888f9e9..2157290c 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -1,27 +1,48 @@ #include "events.h" +#include "../../../USER_SETTINGS.h" +#include "../config.h" + typedef struct { uint32_t timestamp; // Time in seconds since startup when the event occurred uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage uint8_t occurences; // Number of occurrences since startup + uint8_t led_color; // Weirdly indented comment } EVENTS_STRUCT_TYPE; static EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; static unsigned long previous_millis = 0; static uint32_t time_seconds = 0; +static uint8_t total_led_color = GREEN; + +/* Local function prototypes */ +static void print_event_message(EVENTS_ENUM_TYPE event); +static void update_led_color(EVENTS_ENUM_TYPE event); +/* Exported functions */ void init_events(void) { for (uint8_t i = 0; i < EVENT_NOF_EVENTS; i++) { entries[i].timestamp = 0; entries[i].data = 0; entries[i].occurences = 0; + entries[i].led_color = RED; // Most events are RED } + + // YELLOW events below + entries[EVENT_12V_LOW].led_color = YELLOW; + entries[EVENT_CAN_WARNING].led_color = YELLOW; + entries[EVENT_CELL_DEVIATION_HIGH].led_color = YELLOW; + entries[EVENT_KWH_PLAUSIBILITY_ERROR].led_color = YELLOW; } void set_event(EVENTS_ENUM_TYPE event, uint8_t data) { + if (event >= EVENT_NOF_EVENTS) { + event = EVENT_UNKNOWN_EVENT_SET; + } entries[event].timestamp = time_seconds; entries[event].data = data; entries[event].occurences++; + print_event_message(event); } void update_event_timestamps(void) { @@ -31,3 +52,75 @@ void update_event_timestamps(void) { previous_millis = new_millis; } } + +/* Local functions */ +static void update_led_color(EVENTS_ENUM_TYPE event) { + total_led_color = (total_led_color == RED) ? RED : entries[event].led_color; +} + +static void print_event_message(EVENTS_ENUM_TYPE event) { +#ifndef DEBUG_VIA_USB + return; +#else + switch (event) { + case EVENT_CAN_FAILURE: + Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + break; + case EVENT_CAN_WARNING: + Serial.println("ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"); + break; + case EVENT_WATER_INGRESS: + Serial.println("Water leakage inside battery detected. Operation halted. Inspect battery!"); + break; + case EVENT_12V_LOW: + Serial.println( + "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"); + break; + case EVENT_SOC_PLAUSIBILITY_ERROR: + Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!"); + break; + case EVENT_KWH_PLAUSIBILITY_ERROR: + Serial.println("Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); + break; + case EVENT_BATTERY_CHG_STOP_REQ: + Serial.println("ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"); + break; + case EVENT_BATTERY_DISCHG_STOP_REQ: + Serial.println("ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"); + break; + case EVENT_BATTERY_CHG_DISCHG_STOP_REQ: + Serial.println( + "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"); + break; + case EVENT_LOW_SOH: + Serial.println( + "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery."); + break; + case EVENT_HVIL_FAILURE: + Serial.println( + "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be " + "disabled!"); + break; + case EVENT_INTERNAL_OPEN_FAULT: + Serial.println("ERROR: High voltage cable removed while battery running. Opening contactors!"); + break; + case EVENT_CELL_UNDER_VOLTAGE: + Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + break; + case EVENT_CELL_OVER_VOLTAGE: + Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + break; + case EVENT_CELL_DEVIATION_HIGH: + Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + break; + case EVENT_UNKNOWN_EVENT_SET: + Serial.println("An unknown event was set! Review your code!"); + break; + case EVENT_DUMMY: + Serial.println("The dummy event was set!"); + break; + default: + break; + } +#endif +} diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index a23fa608..89573535 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -1,7 +1,9 @@ #ifndef __EVENTS_H__ #define __EVENTS_H__ +#ifndef UNIT_TEST #include +#endif typedef enum { EVENT_CAN_FAILURE = 0u, @@ -10,12 +12,16 @@ typedef enum { EVENT_12V_LOW, EVENT_SOC_PLAUSIBILITY_ERROR, EVENT_KWH_PLAUSIBILITY_ERROR, + EVENT_BATTERY_CHG_STOP_REQ, + EVENT_BATTERY_DISCHG_STOP_REQ, EVENT_BATTERY_CHG_DISCHG_STOP_REQ, EVENT_LOW_SOH, EVENT_HVIL_FAILURE, EVENT_INTERNAL_OPEN_FAULT, EVENT_CELL_UNDER_VOLTAGE, EVENT_CELL_OVER_VOLTAGE, + EVENT_CELL_DEVIATION_HIGH, + EVENT_UNKNOWN_EVENT_SET, EVENT_DUMMY, EVENT_NOF_EVENTS } EVENTS_ENUM_TYPE; diff --git a/Software/src/devboard/utils/events_test.cpp b/Software/src/devboard/utils/events_test.cpp new file mode 100644 index 00000000..cb893252 --- /dev/null +++ b/Software/src/devboard/utils/events_test.cpp @@ -0,0 +1,65 @@ +// The test library must be included first! +#include "../../../test/test_lib.h" + +#include "events.cpp" + +TEST(init_events_test) { + init_events(); + + for (uint8_t i = 0; i < EVENT_NOF_EVENTS; i++) { + ASSERT_EQ(entries[i].occurences, 0); + } +} + +TEST(update_event_timestamps_test) { + // Reset + init_events(); + time_seconds = 0; + + // No delta, so time shouldn't increase + test_millis = 0; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 0); + + // Almost time to bump the seconds + test_millis = 999; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 0); + ASSERT_EQ(previous_millis, 0); + + // millis == 1000, so we should add a second + test_millis = 1000; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 1); + ASSERT_EQ(previous_millis, 1000); + + // We shouldn't add more seconds until 2000 now + test_millis = 1999; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 1); + ASSERT_EQ(previous_millis, 1000); + test_millis = 2000; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 2); + ASSERT_EQ(previous_millis, 2000); +} + +TEST(set_event_test) { + // Reset + init_events(); + time_seconds = 0; + + // Initially, the event should not have any data or occurences + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].data, 0); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].occurences, 0); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 0); + // Set current time and overvoltage event for cell 23 + time_seconds = 345; + set_event(EVENT_CELL_OVER_VOLTAGE, 123); + // Ensure proper event data + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].data, 123); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].occurences, 1); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 345); +} + +TEST_MAIN(); diff --git a/Software/src/devboard/utils/test_commands.txt b/Software/src/devboard/utils/test_commands.txt new file mode 100644 index 00000000..af3b68f6 --- /dev/null +++ b/Software/src/devboard/utils/test_commands.txt @@ -0,0 +1 @@ +g++ events_test.cpp ../../../test/test_lib.cpp -o events_test.exe -DUNIT_TEST -I. \ No newline at end of file diff --git a/Software/test/microtest.h b/Software/test/microtest.h new file mode 100644 index 00000000..36c73aa1 --- /dev/null +++ b/Software/test/microtest.h @@ -0,0 +1,209 @@ +// +// microtest.h +// +// URL: https://github.com/torpedro/microtest.h +// Author: Pedro Flemming (http://torpedro.com/) +// License: MIT License (https://github.com/torpedro/microtest.h/blob/master/LICENSE) +// Copyright (c) 2017 Pedro Flemming +// +// This is a small header-only C++ unit testing framework. +// It allows to define small unit tests with set of assertions available. +// +#ifndef __MICROTEST_H__ +#define __MICROTEST_H__ + +#include +#include +#include +#include + +//////////////// +// Assertions // +//////////////// + +#define ASSERT(cond) ASSERT_TRUE(cond); + +#define ASSERT_TRUE(cond) \ + if (!(cond)) \ + throw mt::AssertFailedException(#cond, __FILE__, __LINE__); + +#define ASSERT_FALSE(cond) \ + if (cond) \ + throw mt::AssertFailedException(#cond, __FILE__, __LINE__); + +#define ASSERT_NULL(value) ASSERT_TRUE(value == NULL); + +#define ASSERT_NOTNULL(value) ASSERT_TRUE(value != NULL); + +#define ASSERT_STREQ(a, b) \ + if (std::string(a).compare(std::string(b)) != 0) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " != " << b << std::endl; \ + throw mt::AssertFailedException(#a " == " #b, __FILE__, __LINE__); \ + } + +#define ASSERT_STRNEQ(a, b) \ + if (std::string(a).compare(std::string(b)) != = 0) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " == " << b << std::endl; \ + throw mt::AssertFailedException(#a " != " #b, __FILE__, __LINE__); \ + } + +#define ASSERT_EQ(a, b) \ + if (a != b) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " != " << b << std::endl; \ + } \ + ASSERT(a == b); + +#define ASSERT_NEQ(a, b) \ + if (a == b) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " == " << b << std::endl; \ + } \ + ASSERT(a != b); + +//////////////// +// Unit Tests // +//////////////// + +#define TEST(name) \ + void name(); \ + namespace { \ + bool __##name = mt::TestsManager::AddTest(name, #name); \ + } \ + void name() + +/////////////// +// Framework // +/////////////// + +namespace mt { + +inline const char* red() { + return "\033[1;31m"; +} + +inline const char* green() { + return "\033[0;32m"; +} + +inline const char* yellow() { + return "\033[0;33m"; +} + +inline const char* def() { + return "\033[0m"; +} + +inline void printRunning(const char* message, FILE* file = stdout) { + fprintf(file, "%s{ running}%s %s\n", green(), def(), message); +} + +inline void printOk(const char* message, FILE* file = stdout) { + fprintf(file, "%s{ ok}%s %s\n", green(), def(), message); +} + +inline void printFailed(const char* message, FILE* file = stdout) { + fprintf(file, "%s{ failed} %s%s\n", red(), message, def()); +} + +// Exception that is thrown when an assertion fails. +class AssertFailedException : public std::exception { + public: + AssertFailedException(std::string description, std::string filepath, int line) + : std::exception(), description_(description), filepath_(filepath), line_(line){}; + + virtual const char* what() const throw() { return description_.c_str(); } + + inline const char* getFilepath() { return filepath_.c_str(); } + + inline int getLine() { return line_; } + + protected: + std::string description_; + std::string filepath_; + int line_; +}; + +class TestsManager { + // Note: static initialization fiasco + // http://www.parashift.com/c++-faq-lite/static-init-order.html + // http://www.parashift.com/c++-faq-lite/static-init-order-on-first-use.html + public: + struct Test { + const char* name; + void (*fn)(void); + }; + + static std::vector& tests() { + static std::vector tests_; + return tests_; + } + + // Adds a new test to the current set of tests. + // Returns false if a test with the same name already exists. + inline static bool AddTest(void (*fn)(void), const char* name) { + tests().push_back({name, fn}); + return true; + } + + // Run all tests that are registered. + // Returns the number of tests that failed. + inline static size_t RunAllTests(FILE* file = stdout) { + size_t num_failed = 0; + + for (const Test& test : tests()) { + // Run the test. + // If an AsserFailedException is thrown, the test has failed. + try { + printRunning(test.name, file); + + (*test.fn)(); + + printOk(test.name, file); + + } catch (AssertFailedException& e) { + printFailed(test.name, file); + fprintf(file, " %sAssertion failed: %s%s\n", red(), e.what(), def()); + fprintf(file, " %s%s:%d%s\n", red(), e.getFilepath(), e.getLine(), def()); + ++num_failed; + } + } + + int return_code = (num_failed > 0) ? 1 : 0; + return return_code; + } +}; + +// Class that will capture the arguments passed to the program. +class Runtime { + public: + static const std::vector& args(int argc = -1, char** argv = NULL) { + static std::vector args_; + if (argc >= 0) { + for (int i = 0; i < argc; ++i) { + args_.push_back(argv[i]); + } + } + return args_; + } +}; +} // namespace mt + +#define TEST_MAIN() \ + int main(int argc, char* argv[]) { \ + mt::Runtime::args(argc, argv); \ + \ + size_t num_failed = mt::TestsManager::RunAllTests(stdout); \ + if (num_failed == 0) { \ + fprintf(stdout, "%s{ summary} All tests succeeded!%s\n", mt::green(), mt::def()); \ + return 0; \ + } else { \ + double percentage = 100.0 * num_failed / mt::TestsManager::tests().size(); \ + fprintf(stderr, "%s{ summary} %lu tests failed (%.2f%%)%s\n", mt::red(), num_failed, percentage, mt::def()); \ + return -1; \ + } \ + } + +#endif // __MICROTEST_H__ diff --git a/Software/test/test_lib.cpp b/Software/test/test_lib.cpp new file mode 100644 index 00000000..372e037c --- /dev/null +++ b/Software/test/test_lib.cpp @@ -0,0 +1,6 @@ +#include "test_lib.h" +#include + +MySerial Serial; + +unsigned long test_millis = 0; diff --git a/Software/test/test_lib.h b/Software/test/test_lib.h new file mode 100644 index 00000000..87296830 --- /dev/null +++ b/Software/test/test_lib.h @@ -0,0 +1,41 @@ +#ifndef __TEST_LIB_H__ +#define __TEST_LIB_H__ + +#include +#include +#include + +#include "microtest.h" + +class MySerial; + +extern unsigned long test_millis; + +static inline unsigned long millis(void) { + return test_millis; +} + +class MySerial { + public: + size_t println(const char* s) { + return print(s, true); // Call print with newline argument true + } + + size_t print(const char* s) { + return print(s, false); // Call print with newline argument false + } + + private: + size_t print(const char* s, bool newline) { + size_t length = printf("%s", s); // Print the string without newline + if (newline) { + printf("\n"); // Add a newline if specified + length++; // Increment length to account for the added newline character + } + return length; // Return the total length printed + } +}; + +extern MySerial Serial; + +#endif From da8421dcff6a1be1636e9d40326117ba0362e7ec Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:42:13 +0100 Subject: [PATCH 03/44] Unit test attempt --- .github/workflows/unit-tests.yml | 23 +++++++ .gitignore | 4 ++ CMakeLists.txt | 6 ++ Software/src/devboard/utils/CMakeLists.txt | 1 + Software/src/devboard/utils/events.cpp | 66 ++++++++++++--------- Software/src/devboard/utils/events.h | 2 + Software/src/devboard/utils/events_test.cpp | 13 ++++ Software/test/CMakeLists.txt | 5 ++ 8 files changed, 92 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/unit-tests.yml create mode 100644 CMakeLists.txt create mode 100644 Software/src/devboard/utils/CMakeLists.txt create mode 100644 Software/test/CMakeLists.txt diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..f5c3c91f --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,23 @@ +name: Build and Test + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Configure and build with CMake + run: | + mkdir build + cd build + cmake .. + cmake --build . + + - name: Run unit tests + run: | + cd build + ./Software/test/Debug/events_test.exe diff --git a/.gitignore b/.gitignore index ea25fe55..c32db3bf 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ # Ignore any files in the build folder Software/build/ +# Ignore CMake build folder +build/ + +# Ignore unit tests *.exe \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..142d28c3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.10) + +project(BatteryEmulator) + +add_subdirectory(Software/src/devboard/utils) +add_subdirectory(Software/test) diff --git a/Software/src/devboard/utils/CMakeLists.txt b/Software/src/devboard/utils/CMakeLists.txt new file mode 100644 index 00000000..bded7cb0 --- /dev/null +++ b/Software/src/devboard/utils/CMakeLists.txt @@ -0,0 +1 @@ +# add_library(utils_library events.cpp) diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 2157290c..06963830 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -14,9 +14,10 @@ static EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; static unsigned long previous_millis = 0; static uint32_t time_seconds = 0; static uint8_t total_led_color = GREEN; +static char event_message[256]; /* Local function prototypes */ -static void print_event_message(EVENTS_ENUM_TYPE event); +static void set_event_message(EVENTS_ENUM_TYPE event); static void update_led_color(EVENTS_ENUM_TYPE event); /* Exported functions */ @@ -42,7 +43,10 @@ void set_event(EVENTS_ENUM_TYPE event, uint8_t data) { entries[event].timestamp = time_seconds; entries[event].data = data; entries[event].occurences++; - print_event_message(event); + set_event_message(event); +#ifdef DEBUG_VIA_USB + Serial.println(event_message); +#endif } void update_event_timestamps(void) { @@ -58,69 +62,75 @@ static void update_led_color(EVENTS_ENUM_TYPE event) { total_led_color = (total_led_color == RED) ? RED : entries[event].led_color; } -static void print_event_message(EVENTS_ENUM_TYPE event) { -#ifndef DEBUG_VIA_USB - return; -#else +static void set_event_message(EVENTS_ENUM_TYPE event) { switch (event) { case EVENT_CAN_FAILURE: - Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + snprintf(event_message, sizeof(event_message), + "No CAN communication detected for 60s. Shutting down battery control."); break; case EVENT_CAN_WARNING: - Serial.println("ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"); + snprintf(event_message, sizeof(event_message), + "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"); break; case EVENT_WATER_INGRESS: - Serial.println("Water leakage inside battery detected. Operation halted. Inspect battery!"); + snprintf(event_message, sizeof(event_message), + "Water leakage inside battery detected. Operation halted. Inspect battery!"); break; case EVENT_12V_LOW: - Serial.println( - "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"); + snprintf(event_message, sizeof(event_message), + "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"); break; case EVENT_SOC_PLAUSIBILITY_ERROR: - Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!"); + snprintf(event_message, sizeof(event_message), "ERROR: SOC% reported by battery not plausible. Restart battery!"); break; case EVENT_KWH_PLAUSIBILITY_ERROR: - Serial.println("Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); + snprintf(event_message, sizeof(event_message), + "Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); break; case EVENT_BATTERY_CHG_STOP_REQ: - Serial.println("ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"); + snprintf(event_message, sizeof(event_message), + "ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"); break; case EVENT_BATTERY_DISCHG_STOP_REQ: - Serial.println("ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"); + snprintf(event_message, sizeof(event_message), + "ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"); break; case EVENT_BATTERY_CHG_DISCHG_STOP_REQ: - Serial.println( - "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"); + snprintf(event_message, sizeof(event_message), + "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"); break; case EVENT_LOW_SOH: - Serial.println( + snprintf( + event_message, sizeof(event_message), "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery."); break; case EVENT_HVIL_FAILURE: - Serial.println( - "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be " - "disabled!"); + snprintf(event_message, sizeof(event_message), + "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be " + "disabled!"); break; case EVENT_INTERNAL_OPEN_FAULT: - Serial.println("ERROR: High voltage cable removed while battery running. Opening contactors!"); + snprintf(event_message, sizeof(event_message), + "ERROR: High voltage cable removed while battery running. Opening contactors!"); break; case EVENT_CELL_UNDER_VOLTAGE: - Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + snprintf(event_message, sizeof(event_message), + "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); break; case EVENT_CELL_OVER_VOLTAGE: - Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + snprintf(event_message, sizeof(event_message), + "ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); break; case EVENT_CELL_DEVIATION_HIGH: - Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + snprintf(event_message, sizeof(event_message), "ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); break; case EVENT_UNKNOWN_EVENT_SET: - Serial.println("An unknown event was set! Review your code!"); + snprintf(event_message, sizeof(event_message), "An unknown event was set! Review your code!"); break; case EVENT_DUMMY: - Serial.println("The dummy event was set!"); + snprintf(event_message, sizeof(event_message), "The dummy event was set!"); // Don't change this event message! break; default: break; } -#endif } diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index 89573535..58abb58e 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -5,6 +5,8 @@ #include #endif +#include + typedef enum { EVENT_CAN_FAILURE = 0u, EVENT_CAN_WARNING, diff --git a/Software/src/devboard/utils/events_test.cpp b/Software/src/devboard/utils/events_test.cpp index cb893252..6678db2d 100644 --- a/Software/src/devboard/utils/events_test.cpp +++ b/Software/src/devboard/utils/events_test.cpp @@ -3,6 +3,11 @@ #include "events.cpp" +/* Helper functions */ +static void reset_event_msg(void) { + snprintf(event_message, sizeof(event_message), ""); +} + TEST(init_events_test) { init_events(); @@ -62,4 +67,12 @@ TEST(set_event_test) { ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 345); } +TEST(event_message_test) { + reset_event_msg(); + + set_event(EVENT_DUMMY, 0); // Set dummy event with no data + + ASSERT_STREQ("The dummy event was set!", event_message); +} + TEST_MAIN(); diff --git a/Software/test/CMakeLists.txt b/Software/test/CMakeLists.txt new file mode 100644 index 00000000..30d2430d --- /dev/null +++ b/Software/test/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(events_test ../src/devboard/utils/events_test.cpp test_lib.cpp) + +target_compile_definitions(events_test PRIVATE UNIT_TEST) + +target_link_libraries(events_test) # Link to the library from devboard/utils From 396f41ed7750e6b44b493d4d855b95ed9db26322 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:53:10 +0100 Subject: [PATCH 04/44] Moved test folder previous location caused issues with building the main SW --- CMakeLists.txt | 2 +- Software/src/devboard/utils/events_test.cpp | 2 +- {Software/test => test}/CMakeLists.txt | 2 +- {Software/test => test}/microtest.h | 0 {Software/test => test}/test_lib.cpp | 0 {Software/test => test}/test_lib.h | 0 6 files changed, 3 insertions(+), 3 deletions(-) rename {Software/test => test}/CMakeLists.txt (61%) rename {Software/test => test}/microtest.h (100%) rename {Software/test => test}/test_lib.cpp (100%) rename {Software/test => test}/test_lib.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 142d28c3..3239de2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,4 +3,4 @@ cmake_minimum_required(VERSION 3.10) project(BatteryEmulator) add_subdirectory(Software/src/devboard/utils) -add_subdirectory(Software/test) +add_subdirectory(test) diff --git a/Software/src/devboard/utils/events_test.cpp b/Software/src/devboard/utils/events_test.cpp index 6678db2d..1c38a888 100644 --- a/Software/src/devboard/utils/events_test.cpp +++ b/Software/src/devboard/utils/events_test.cpp @@ -1,5 +1,5 @@ // The test library must be included first! -#include "../../../test/test_lib.h" +#include "../../../../test/test_lib.h" #include "events.cpp" diff --git a/Software/test/CMakeLists.txt b/test/CMakeLists.txt similarity index 61% rename from Software/test/CMakeLists.txt rename to test/CMakeLists.txt index 30d2430d..d12a405e 100644 --- a/Software/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(events_test ../src/devboard/utils/events_test.cpp test_lib.cpp) +add_executable(events_test ../Software/src/devboard/utils/events_test.cpp test_lib.cpp) target_compile_definitions(events_test PRIVATE UNIT_TEST) diff --git a/Software/test/microtest.h b/test/microtest.h similarity index 100% rename from Software/test/microtest.h rename to test/microtest.h diff --git a/Software/test/test_lib.cpp b/test/test_lib.cpp similarity index 100% rename from Software/test/test_lib.cpp rename to test/test_lib.cpp diff --git a/Software/test/test_lib.h b/test/test_lib.h similarity index 100% rename from Software/test/test_lib.h rename to test/test_lib.h From f4fa1115c1df4a40113e4038119000477d0f23ba Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:55:27 +0100 Subject: [PATCH 05/44] UT debugging --- .github/workflows/unit-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index f5c3c91f..917a9f1f 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -19,5 +19,7 @@ jobs: - name: Run unit tests run: | + dir cd build + dir ./Software/test/Debug/events_test.exe From 5785eeb111807eee48989477d3fd905974c08425 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 22:57:35 +0100 Subject: [PATCH 06/44] Update unit-tests.yml --- .github/workflows/unit-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 917a9f1f..79cbb3f4 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -20,6 +20,6 @@ jobs: - name: Run unit tests run: | dir - cd build + cd build/test dir - ./Software/test/Debug/events_test.exe + ./test/Debug/events_test.exe From 92fd0ac31b4552d13470a7f37bad31712ad5e54d Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:01:44 +0100 Subject: [PATCH 07/44] Update unit-tests.yml --- .github/workflows/unit-tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 79cbb3f4..b1e3bf8b 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -21,5 +21,7 @@ jobs: run: | dir cd build/test - dir - ./test/Debug/events_test.exe + dir -s + cd events_test + dir -s + ./events_test.exe From 933d11f932ab8502b65585560c680e8402dca681 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:03:34 +0100 Subject: [PATCH 08/44] More UT --- .github/workflows/unit-tests.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index b1e3bf8b..bf7c8895 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -19,9 +19,5 @@ jobs: - name: Run unit tests run: | - dir cd build/test - dir -s - cd events_test - dir -s - ./events_test.exe + ./events_test From f1bff798c4589dbec8626becf722f180be87a428 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:11:57 +0100 Subject: [PATCH 09/44] Moved test files --- test/CMakeLists.txt | 8 +++++++- {Software/src/devboard/utils => test}/events_test.cpp | 0 2 files changed, 7 insertions(+), 1 deletion(-) rename {Software/src/devboard/utils => test}/events_test.cpp (100%) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d12a405e..b445ece9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,11 @@ -add_executable(events_test ../Software/src/devboard/utils/events_test.cpp test_lib.cpp) +# Include the directory with your source files +include_directories(${CMAKE_SOURCE_DIR}/Software/src/devboard/utils) + +add_executable(events_test events_test.cpp test_lib.cpp) target_compile_definitions(events_test PRIVATE UNIT_TEST) target_link_libraries(events_test) # Link to the library from devboard/utils + +# Register your tests with CTest +add_test(NAME MyProjectTests COMMAND events_test) diff --git a/Software/src/devboard/utils/events_test.cpp b/test/events_test.cpp similarity index 100% rename from Software/src/devboard/utils/events_test.cpp rename to test/events_test.cpp From dcd8dcade98f3e855a5e6f52b025eb60fbd4e5be Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:13:25 +0100 Subject: [PATCH 10/44] Name change --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index bf7c8895..df92a4e6 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -1,4 +1,4 @@ -name: Build and Test +name: Run Unit Tests on: [push] From 7f6eddca76a3da2e3f0f444c666bd63729f3eecc Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:15:54 +0100 Subject: [PATCH 11/44] Testing for failed unit tests --- Software/src/devboard/utils/events.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 06963830..50b51453 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -52,7 +52,7 @@ void set_event(EVENTS_ENUM_TYPE event, uint8_t data) { void update_event_timestamps(void) { unsigned long new_millis = millis(); if (new_millis - previous_millis >= 1000) { - time_seconds++; + //time_seconds++; previous_millis = new_millis; } } From 5edfd6cdec272f8c8dc7fdee3ee21b9a8bcd9507 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:16:46 +0100 Subject: [PATCH 12/44] Restore to working order, success! --- Software/src/devboard/utils/events.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 50b51453..06963830 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -52,7 +52,7 @@ void set_event(EVENTS_ENUM_TYPE event, uint8_t data) { void update_event_timestamps(void) { unsigned long new_millis = millis(); if (new_millis - previous_millis >= 1000) { - //time_seconds++; + time_seconds++; previous_millis = new_millis; } } From 1d09dacccb1424edc13fcc54bede3ed869047498 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:36:25 +0100 Subject: [PATCH 13/44] Event setting --- Software/src/battery/BMW-I3-BATTERY.cpp | 2 ++ Software/src/battery/CHADEMO-BATTERY.cpp | 2 ++ Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp | 2 ++ Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp | 7 +++++++ Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 12 ++++++++++++ Software/src/battery/RENAULT-KANGOO-BATTERY.cpp | 5 +++++ Software/src/battery/RENAULT-ZOE-BATTERY.cpp | 5 +++++ Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp | 2 ++ Software/src/battery/TESLA-MODEL-3-BATTERY.cpp | 11 +++++++++++ 9 files changed, 48 insertions(+) diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 935da03e..40acb0e2 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -1,4 +1,5 @@ #include "BMW-I3-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -103,6 +104,7 @@ void update_values_i3_battery() { //This function maps all the values fetched v if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 929c0a15..84a3634d 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -1,4 +1,5 @@ #include "CHADEMO-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -107,6 +108,7 @@ void update_values_chademo_battery() { //This function maps all the values fetc bms_status = FAULT; errorCode = 7; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index f9a4d97d..51ebdba9 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -1,4 +1,5 @@ #include "IMIEV-CZERO-ION-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -109,6 +110,7 @@ void update_values_imiev_battery() { //This function maps all the values fetche if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 40c948cd..e1b27df7 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -1,4 +1,5 @@ #include "KIA-HYUNDAI-64-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -200,6 +201,7 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -207,11 +209,13 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (waterleakageSensor == 0) { Serial.println("Water leakage inside battery detected. Operation halted. Inspect battery!"); bms_status = FAULT; + set_event(EVENT_WATER_INGRESS, 0); } if (leadAcidBatteryVoltage < 110) { Serial.println("12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"); LEDcolor = YELLOW; + set_event(EVENT_12V_LOW, leadAcidBatteryVoltage); } // Check if cell voltages are within allowed range @@ -220,14 +224,17 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (cell_max_voltage >= MAX_CELL_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (cell_min_voltage <= MIN_CELL_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } if (bms_status == FAULT) { //Incase we enter a critical fault state, zero out the allowed limits diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 17a28679..835487ee 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -2,6 +2,7 @@ #ifdef MQTT #include "../devboard/mqtt/mqtt.h" #endif +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -262,6 +263,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!"); #endif + set_event(EVENT_SOC_PLAUSIBILITY_ERROR, LB_SOC / 10); } } @@ -311,6 +313,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"); #endif + set_event(EVENT_BATTERY_DISCHG_STOP_REQ, 0); break; case (6): //Caution Lamp Request & Charging Mode Stop Request @@ -319,6 +322,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"); #endif + set_event(EVENT_BATTERY_CHG_STOP_REQ, 0); break; case (7): //Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request @@ -328,6 +332,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched Serial.println( "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"); #endif + set_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ, 0); break; default: break; @@ -342,6 +347,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #endif bms_status = FAULT; errorCode = 5; + set_event(EVENT_LOW_SOH, LB_StateOfHealth); max_target_discharge_power = 0; max_target_charge_power = 0; } @@ -355,6 +361,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched "disabled!"); #endif bms_status = FAULT; + set_event(EVENT_HVIL_FAILURE, 0); errorCode = 6; SOC = 0; max_target_discharge_power = 0; @@ -369,6 +376,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control."); #endif + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -380,6 +388,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"); #endif + set_event(EVENT_CAN_WARNING, 0); } /*Finally print out values to serial if configured to do so*/ @@ -615,6 +624,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("HIGH CELL DEVIATION!!! Inspect battery!"); #endif + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) { @@ -623,6 +633,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); #endif + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (min_max_voltage[0] <= MIN_CELL_VOLTAGE) { bms_status = FAULT; @@ -630,6 +641,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); #endif + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } break; } diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index 4f818878..47ab1f64 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -1,4 +1,5 @@ #include "RENAULT-KANGOO-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -126,6 +127,7 @@ void update_values_kangoo_battery() { //This function maps all the values fetch if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -133,14 +135,17 @@ void update_values_kangoo_battery() { //This function maps all the values fetch if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL mV DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } #ifdef DEBUG_VIA_USB diff --git a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp index b65b71ec..d40f09cf 100644 --- a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp @@ -1,4 +1,5 @@ #include "RENAULT-ZOE-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -87,6 +88,7 @@ void update_values_zoe_battery() { //This function maps all the values fetched if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -94,14 +96,17 @@ void update_values_zoe_battery() { //This function maps all the values fetched if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL mV DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } #ifdef DEBUG_VIA_USB diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index 2959a684..e2aaa66f 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -1,4 +1,5 @@ #include "SANTA-FE-PHEV-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -84,6 +85,7 @@ void update_values_santafe_phev_battery() { //This function maps all the values if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index 59aa7c18..fb05806a 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -1,4 +1,5 @@ #include "TESLA-MODEL-3-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -231,6 +232,7 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (!stillAliveCAN) { bms_status = FAULT; Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { stillAliveCAN--; } @@ -238,6 +240,7 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (hvil_status == 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use bms_status = FAULT; Serial.println("ERROR: High voltage cable removed while battery running. Opening contactors!"); + set_event(EVENT_INTERNAL_OPEN_FAULT, 0); } cell_deviation_mV = (cell_max_v - cell_min_v); @@ -258,12 +261,14 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (SOC < 6500) { //When SOC is less than 65.00% when approaching max voltage bms_status = FAULT; Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!"); + set_event(EVENT_SOC_PLAUSIBILITY_ERROR, SOC / 100); } } //Check if BMS is in need of recalibration if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) { Serial.println("Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); + set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy); LEDcolor = YELLOW; } @@ -271,27 +276,33 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (cell_max_v >= MAX_CELL_VOLTAGE_LFP) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (cell_min_v <= MIN_CELL_VOLTAGE_LFP) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_LFP) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } } else { //NCA/NCM limits used if (cell_max_v >= MAX_CELL_VOLTAGE_NCA_NCM) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (cell_min_v <= MIN_CELL_VOLTAGE_NCA_NCM) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_NCA_NCM) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } } From aca520c5062777d8e718b3819925e120f0eb9ba0 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue, 6 Feb 2024 07:55:00 +0100 Subject: [PATCH 14/44] Added a test, new structure --- .github/workflows/unit-tests.yml | 2 +- CMakeLists.txt | 2 +- Software/src/devboard/utils/CMakeLists.txt | 1 - Software/src/devboard/utils/timer.cpp | 10 ++++--- Software/src/devboard/utils/timer.h | 4 ++- test/CMakeLists.txt | 19 ++++++++----- test/test_lib.cpp | 2 +- test/test_lib.h | 6 +++-- test/{ => utils}/events_test.cpp | 12 ++++----- test/utils/timer_test.cpp | 31 ++++++++++++++++++++++ 10 files changed, 66 insertions(+), 23 deletions(-) delete mode 100644 Software/src/devboard/utils/CMakeLists.txt rename test/{ => utils}/events_test.cpp (92%) create mode 100644 test/utils/timer_test.cpp diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index df92a4e6..1381259a 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -20,4 +20,4 @@ jobs: - name: Run unit tests run: | cd build/test - ./events_test + find . -type f -executable -exec {} \; diff --git a/CMakeLists.txt b/CMakeLists.txt index 3239de2d..6f0c4af6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,5 +2,5 @@ cmake_minimum_required(VERSION 3.10) project(BatteryEmulator) -add_subdirectory(Software/src/devboard/utils) +# add_subdirectory(Software/src/devboard/utils) add_subdirectory(test) diff --git a/Software/src/devboard/utils/CMakeLists.txt b/Software/src/devboard/utils/CMakeLists.txt deleted file mode 100644 index bded7cb0..00000000 --- a/Software/src/devboard/utils/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -# add_library(utils_library events.cpp) diff --git a/Software/src/devboard/utils/timer.cpp b/Software/src/devboard/utils/timer.cpp index 5b4d59bf..ff051a03 100644 --- a/Software/src/devboard/utils/timer.cpp +++ b/Software/src/devboard/utils/timer.cpp @@ -1,11 +1,13 @@ #include "timer.h" -MyTimer::MyTimer(unsigned long interval) : interval(interval), previousMillis(0) {} +MyTimer::MyTimer(unsigned long interval) : interval(interval) { + previous_millis = millis(); +} bool MyTimer::elapsed() { - unsigned long currentMillis = millis(); - if (currentMillis - previousMillis >= interval) { - previousMillis = currentMillis; + unsigned long current_millis = millis(); + if (current_millis - previous_millis >= interval) { + previous_millis = current_millis; return true; } return false; diff --git a/Software/src/devboard/utils/timer.h b/Software/src/devboard/utils/timer.h index 829ea1e5..e330eb2f 100644 --- a/Software/src/devboard/utils/timer.h +++ b/Software/src/devboard/utils/timer.h @@ -1,7 +1,9 @@ #ifndef __MYTIMER_H__ #define __MYTIMER_H__ +#ifndef UNIT_TEST #include +#endif class MyTimer { public: @@ -12,7 +14,7 @@ class MyTimer { private: unsigned long interval; - unsigned long previousMillis; + unsigned long previous_millis; }; #endif // __MYTIMER_H__ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b445ece9..6d63e2e3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,11 +1,18 @@ # Include the directory with your source files -include_directories(${CMAKE_SOURCE_DIR}/Software/src/devboard/utils) +include_directories(${CMAKE_SOURCE_DIR}/Software/src/devboard/utils .) -add_executable(events_test events_test.cpp test_lib.cpp) +# Create a variable to store the list of test files +file(GLOB TEST_SOURCES utils/*.cpp) -target_compile_definitions(events_test PRIVATE UNIT_TEST) +# 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) -target_link_libraries(events_test) # Link to the library from devboard/utils + # Create an executable for the test + add_executable(${TEST_NAME} ${TEST_SOURCE} test_lib.cpp) -# Register your tests with CTest -add_test(NAME MyProjectTests COMMAND events_test) + # Apply the target_compile_definitions for the test + target_compile_definitions(${TEST_NAME} PRIVATE UNIT_TEST) + +endforeach() diff --git a/test/test_lib.cpp b/test/test_lib.cpp index 372e037c..8ed9c2f5 100644 --- a/test/test_lib.cpp +++ b/test/test_lib.cpp @@ -3,4 +3,4 @@ MySerial Serial; -unsigned long test_millis = 0; +unsigned long testlib_millis = 0; diff --git a/test/test_lib.h b/test/test_lib.h index 87296830..ccca3ca4 100644 --- a/test/test_lib.h +++ b/test/test_lib.h @@ -9,12 +9,14 @@ class MySerial; -extern unsigned long test_millis; +extern unsigned long testlib_millis; +/* Mock millis() */ static inline unsigned long millis(void) { - return test_millis; + return testlib_millis; } +/* Mock Serial class */ class MySerial { public: size_t println(const char* s) { diff --git a/test/events_test.cpp b/test/utils/events_test.cpp similarity index 92% rename from test/events_test.cpp rename to test/utils/events_test.cpp index 1c38a888..1d6c0748 100644 --- a/test/events_test.cpp +++ b/test/utils/events_test.cpp @@ -1,5 +1,5 @@ // The test library must be included first! -#include "../../../../test/test_lib.h" +#include "test_lib.h" #include "events.cpp" @@ -22,28 +22,28 @@ TEST(update_event_timestamps_test) { time_seconds = 0; // No delta, so time shouldn't increase - test_millis = 0; + testlib_millis = 0; update_event_timestamps(); ASSERT_EQ(time_seconds, 0); // Almost time to bump the seconds - test_millis = 999; + testlib_millis = 999; update_event_timestamps(); ASSERT_EQ(time_seconds, 0); ASSERT_EQ(previous_millis, 0); // millis == 1000, so we should add a second - test_millis = 1000; + testlib_millis = 1000; update_event_timestamps(); ASSERT_EQ(time_seconds, 1); ASSERT_EQ(previous_millis, 1000); // We shouldn't add more seconds until 2000 now - test_millis = 1999; + testlib_millis = 1999; update_event_timestamps(); ASSERT_EQ(time_seconds, 1); ASSERT_EQ(previous_millis, 1000); - test_millis = 2000; + testlib_millis = 2000; update_event_timestamps(); ASSERT_EQ(time_seconds, 2); ASSERT_EQ(previous_millis, 2000); diff --git a/test/utils/timer_test.cpp b/test/utils/timer_test.cpp new file mode 100644 index 00000000..613bf739 --- /dev/null +++ b/test/utils/timer_test.cpp @@ -0,0 +1,31 @@ +// The test library must be included first! +#include "timer.cpp" +#include "../test_lib.h" + +/* Helper functions */ + +/* Test functions */ + +TEST(timer_test) { + unsigned long test_interval = 10; + + testlib_millis = 0; + MyTimer timer(test_interval); + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = test_interval - 1; + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = test_interval; + ASSERT_EQ(timer.elapsed(), true); + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = 2 * test_interval - 1; + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = 2 * test_interval; + ASSERT_EQ(timer.elapsed(), true); + ASSERT_EQ(timer.elapsed(), false); +} + +TEST_MAIN(); From a81133c6c01de9b140ce58da15565624e4385264 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue, 6 Feb 2024 07:59:22 +0100 Subject: [PATCH 15/44] Update timer_test.cpp --- test/utils/timer_test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/utils/timer_test.cpp b/test/utils/timer_test.cpp index 613bf739..96e9b142 100644 --- a/test/utils/timer_test.cpp +++ b/test/utils/timer_test.cpp @@ -1,7 +1,8 @@ // The test library must be included first! -#include "timer.cpp" #include "../test_lib.h" +#include "timer.cpp" + /* Helper functions */ /* Test functions */ From 4577df094e58c67c0f195738a74967798f2b97dc Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue, 6 Feb 2024 08:17:47 +0100 Subject: [PATCH 16/44] Some updates, temp disable other Actions --- .github/workflows/compile-all-batteries.yml | 6 +--- .github/workflows/compile-all-inverters.yml | 6 +--- .github/workflows/run-pre-commit.yml | 4 +-- .github/workflows/unit-tests.yml | 3 +- Software/src/devboard/utils/CMakeLists.txt | 1 + test/utils/timer_test.cpp | 40 ++++++++++++--------- 6 files changed, 30 insertions(+), 30 deletions(-) create mode 100644 Software/src/devboard/utils/CMakeLists.txt diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index c0c1493e..dcb9a1f6 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -2,11 +2,7 @@ name: Compile All Batteries # Here we tell GitHub when to run the workflow. -on: - # The workflow is run when a commit is pushed or for a - # Pull Request. - - push - - pull_request +on: [push, pull_request] # This is the list of jobs that will be run concurrently. # Since we use a build matrix, the actual number of jobs diff --git a/.github/workflows/compile-all-inverters.yml b/.github/workflows/compile-all-inverters.yml index 954e263c..f33f0d26 100644 --- a/.github/workflows/compile-all-inverters.yml +++ b/.github/workflows/compile-all-inverters.yml @@ -2,11 +2,7 @@ name: Compile All Inverters # Here we tell GitHub when to run the workflow. -on: - # The workflow is run when a commit is pushed or for a - # Pull Request. - - push - - pull_request +on: [push, pull_request] # This is the list of jobs that will be run concurrently. # Since we use a build matrix, the actual number of jobs diff --git a/.github/workflows/run-pre-commit.yml b/.github/workflows/run-pre-commit.yml index e23a9655..aedd71f8 100644 --- a/.github/workflows/run-pre-commit.yml +++ b/.github/workflows/run-pre-commit.yml @@ -2,9 +2,7 @@ name: Run pre-commit -on: - - push - - pull_request +on: [push, pull_request] jobs: pre-commit: diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 1381259a..4ca1782d 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -1,6 +1,6 @@ name: Run Unit Tests -on: [push] +on: [push, pull_request] jobs: build: @@ -19,5 +19,6 @@ jobs: - name: Run unit tests run: | + set -e # Exit immediately on non-zero exit code cd build/test find . -type f -executable -exec {} \; diff --git a/Software/src/devboard/utils/CMakeLists.txt b/Software/src/devboard/utils/CMakeLists.txt new file mode 100644 index 00000000..bded7cb0 --- /dev/null +++ b/Software/src/devboard/utils/CMakeLists.txt @@ -0,0 +1 @@ +# add_library(utils_library events.cpp) diff --git a/test/utils/timer_test.cpp b/test/utils/timer_test.cpp index 96e9b142..52c64f13 100644 --- a/test/utils/timer_test.cpp +++ b/test/utils/timer_test.cpp @@ -9,24 +9,32 @@ TEST(timer_test) { unsigned long test_interval = 10; + bool result; - testlib_millis = 0; + testlib_millis = 30; MyTimer timer(test_interval); - ASSERT_EQ(timer.elapsed(), false); - - testlib_millis = test_interval - 1; - ASSERT_EQ(timer.elapsed(), false); - - testlib_millis = test_interval; - ASSERT_EQ(timer.elapsed(), true); - ASSERT_EQ(timer.elapsed(), false); - - testlib_millis = 2 * test_interval - 1; - ASSERT_EQ(timer.elapsed(), false); - - testlib_millis = 2 * test_interval; - ASSERT_EQ(timer.elapsed(), true); - ASSERT_EQ(timer.elapsed(), false); + result = timer.elapsed(); + ASSERT_EQ(result, false); + + testlib_millis += test_interval - 1; + result = timer.elapsed(); + ASSERT_EQ(result, false); + + testlib_millis += 1; + result = timer.elapsed(); + ASSERT_EQ(result, true); + result = timer.elapsed(); + ASSERT_EQ(result, false); + + testlib_millis += test_interval - 1; + result = timer.elapsed(); + ASSERT_EQ(result, false); + + testlib_millis += 1; + result = timer.elapsed(); + ASSERT_EQ(result, true); + result = timer.elapsed(); + ASSERT_EQ(result, true); // Injected fault to catch unit test errors } TEST_MAIN(); From 7e4bd653bf83b502b63a2fa6ded83a7497055898 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue, 6 Feb 2024 08:19:56 +0100 Subject: [PATCH 17/44] Trying to fail... --- .github/workflows/compile-all-batteries.yml | 2 +- .github/workflows/compile-all-inverters.yml | 2 +- .github/workflows/run-pre-commit.yml | 2 +- .github/workflows/unit-tests.yml | 6 +++++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index dcb9a1f6..01fe6802 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -2,7 +2,7 @@ name: Compile All Batteries # Here we tell GitHub when to run the workflow. -on: [push, pull_request] +# on: [push, pull_request] # This is the list of jobs that will be run concurrently. # Since we use a build matrix, the actual number of jobs diff --git a/.github/workflows/compile-all-inverters.yml b/.github/workflows/compile-all-inverters.yml index f33f0d26..e4339807 100644 --- a/.github/workflows/compile-all-inverters.yml +++ b/.github/workflows/compile-all-inverters.yml @@ -2,7 +2,7 @@ name: Compile All Inverters # Here we tell GitHub when to run the workflow. -on: [push, pull_request] +# on: [push, pull_request] # This is the list of jobs that will be run concurrently. # Since we use a build matrix, the actual number of jobs diff --git a/.github/workflows/run-pre-commit.yml b/.github/workflows/run-pre-commit.yml index aedd71f8..6abd4ae0 100644 --- a/.github/workflows/run-pre-commit.yml +++ b/.github/workflows/run-pre-commit.yml @@ -2,7 +2,7 @@ name: Run pre-commit -on: [push, pull_request] +# on: [push, pull_request] jobs: pre-commit: diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 4ca1782d..6bf3043f 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -21,4 +21,8 @@ jobs: run: | set -e # Exit immediately on non-zero exit code cd build/test - find . -type f -executable -exec {} \; + for test_executable in utils/*; do + if [ -x "$test_executable" ]; then + ./"$test_executable" + fi + done From 041be95b292d0773dbb4e89425ecf2042e4a502a Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue, 6 Feb 2024 08:22:13 +0100 Subject: [PATCH 18/44] Close! --- .github/workflows/compile-all-batteries.yml | 2 +- .github/workflows/compile-all-inverters.yml | 2 +- .github/workflows/run-pre-commit.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/compile-all-batteries.yml b/.github/workflows/compile-all-batteries.yml index 01fe6802..dcb9a1f6 100644 --- a/.github/workflows/compile-all-batteries.yml +++ b/.github/workflows/compile-all-batteries.yml @@ -2,7 +2,7 @@ name: Compile All Batteries # Here we tell GitHub when to run the workflow. -# on: [push, pull_request] +on: [push, pull_request] # This is the list of jobs that will be run concurrently. # Since we use a build matrix, the actual number of jobs diff --git a/.github/workflows/compile-all-inverters.yml b/.github/workflows/compile-all-inverters.yml index e4339807..f33f0d26 100644 --- a/.github/workflows/compile-all-inverters.yml +++ b/.github/workflows/compile-all-inverters.yml @@ -2,7 +2,7 @@ name: Compile All Inverters # Here we tell GitHub when to run the workflow. -# on: [push, pull_request] +on: [push, pull_request] # This is the list of jobs that will be run concurrently. # Since we use a build matrix, the actual number of jobs diff --git a/.github/workflows/run-pre-commit.yml b/.github/workflows/run-pre-commit.yml index 6abd4ae0..aedd71f8 100644 --- a/.github/workflows/run-pre-commit.yml +++ b/.github/workflows/run-pre-commit.yml @@ -2,7 +2,7 @@ name: Run pre-commit -# on: [push, pull_request] +on: [push, pull_request] jobs: pre-commit: diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 6bf3043f..e0a6a971 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -21,7 +21,7 @@ jobs: run: | set -e # Exit immediately on non-zero exit code cd build/test - for test_executable in utils/*; do + for test_executable in *; do if [ -x "$test_executable" ]; then ./"$test_executable" fi From a8a2eb46c6301e6a11fabc5b0832ad7bf854fe71 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue, 6 Feb 2024 08:23:16 +0100 Subject: [PATCH 19/44] Update unit-tests.yml --- .github/workflows/unit-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index e0a6a971..4e81ef0a 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -21,6 +21,7 @@ jobs: run: | set -e # Exit immediately on non-zero exit code cd build/test + dir -s for test_executable in *; do if [ -x "$test_executable" ]; then ./"$test_executable" From 88dc5fece456a18075f2186bced92c362a0389d1 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue, 6 Feb 2024 08:25:06 +0100 Subject: [PATCH 20/44] Update unit-tests.yml Trying t filter out directories... --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 4e81ef0a..ba59c71c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -23,7 +23,7 @@ jobs: cd build/test dir -s for test_executable in *; do - if [ -x "$test_executable" ]; then + if [ -f "$test_executable" ] && [ -x "$test_executable" ]; then ./"$test_executable" fi done From 12136fa189360579656cfe03adb543dc656b273f Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue, 6 Feb 2024 08:25:58 +0100 Subject: [PATCH 21/44] Restore the timer test --- test/utils/timer_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/timer_test.cpp b/test/utils/timer_test.cpp index 52c64f13..da527779 100644 --- a/test/utils/timer_test.cpp +++ b/test/utils/timer_test.cpp @@ -34,7 +34,7 @@ TEST(timer_test) { result = timer.elapsed(); ASSERT_EQ(result, true); result = timer.elapsed(); - ASSERT_EQ(result, true); // Injected fault to catch unit test errors + ASSERT_EQ(result, false); } TEST_MAIN(); From 6bc253ff6e9c2ca7c3b900411c8e057b64fbfad8 Mon Sep 17 00:00:00 2001 From: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue, 6 Feb 2024 08:32:53 +0100 Subject: [PATCH 22/44] Comments --- test/utils/timer_test.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/utils/timer_test.cpp b/test/utils/timer_test.cpp index da527779..268c9021 100644 --- a/test/utils/timer_test.cpp +++ b/test/utils/timer_test.cpp @@ -11,25 +11,32 @@ TEST(timer_test) { unsigned long test_interval = 10; bool result; + // Create a timer, assert that it hasn't elapsed immediately testlib_millis = 30; MyTimer timer(test_interval); result = timer.elapsed(); ASSERT_EQ(result, false); + // Test interval - 1, shouldn't have elapsed testlib_millis += test_interval - 1; result = timer.elapsed(); ASSERT_EQ(result, false); + // Add 1, so now it should have elapsed testlib_millis += 1; result = timer.elapsed(); ASSERT_EQ(result, true); + + // The timer should have reset when it elapsed result = timer.elapsed(); ASSERT_EQ(result, false); + // Test close to the next interval testlib_millis += test_interval - 1; result = timer.elapsed(); ASSERT_EQ(result, false); + // Add 1, ensure that the timer elapses but only once testlib_millis += 1; result = timer.elapsed(); ASSERT_EQ(result, true); From cb935e4cf8bfa1892dec40b2466f01d0e6eb723b Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Tue, 6 Feb 2024 20:06:07 +1100 Subject: [PATCH 23/44] Squashed commit of the following: commit a81133c6c01de9b140ce58da15565624e4385264 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue Feb 6 07:59:22 2024 +0100 Update timer_test.cpp commit aca520c5062777d8e718b3819925e120f0eb9ba0 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Tue Feb 6 07:55:00 2024 +0100 Added a test, new structure commit 1d09dacccb1424edc13fcc54bede3ed869047498 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:36:25 2024 +0100 Event setting commit 5edfd6cdec272f8c8dc7fdee3ee21b9a8bcd9507 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:16:46 2024 +0100 Restore to working order, success! commit 7f6eddca76a3da2e3f0f444c666bd63729f3eecc Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:15:54 2024 +0100 Testing for failed unit tests commit dcd8dcade98f3e855a5e6f52b025eb60fbd4e5be Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:13:25 2024 +0100 Name change commit f1bff798c4589dbec8626becf722f180be87a428 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:11:57 2024 +0100 Moved test files commit 933d11f932ab8502b65585560c680e8402dca681 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:03:34 2024 +0100 More UT commit 92fd0ac31b4552d13470a7f37bad31712ad5e54d Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 23:01:44 2024 +0100 Update unit-tests.yml commit 5785eeb111807eee48989477d3fd905974c08425 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 22:57:35 2024 +0100 Update unit-tests.yml commit f4fa1115c1df4a40113e4038119000477d0f23ba Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 22:55:27 2024 +0100 UT debugging commit 396f41ed7750e6b44b493d4d855b95ed9db26322 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 22:53:10 2024 +0100 Moved test folder previous location caused issues with building the main SW commit da8421dcff6a1be1636e9d40326117ba0362e7ec Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 22:42:13 2024 +0100 Unit test attempt commit 44c02745bf941637f0ef3bbb006e5064550533b2 Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 14:10:12 2024 +0100 Improvements and unit tests commit 4d95fcfc31559e1777cac3357fcff13816b6a16e Author: Cabooman <81711263+Cabooman@users.noreply.github.com> Date: Mon Feb 5 01:19:37 2024 +0100 First commit --- .github/workflows/unit-tests.yml | 23 ++ .gitignore | 6 + CMakeLists.txt | 6 + Software/Software.ino | 5 + Software/USER_SETTINGS.h | 4 +- Software/src/battery/BMW-I3-BATTERY.cpp | 2 + Software/src/battery/CHADEMO-BATTERY.cpp | 2 + .../src/battery/IMIEV-CZERO-ION-BATTERY.cpp | 2 + .../src/battery/KIA-HYUNDAI-64-BATTERY.cpp | 7 + Software/src/battery/NISSAN-LEAF-BATTERY.cpp | 12 + .../src/battery/RENAULT-KANGOO-BATTERY.cpp | 5 + Software/src/battery/RENAULT-ZOE-BATTERY.cpp | 5 + .../src/battery/SANTA-FE-PHEV-BATTERY.cpp | 2 + .../src/battery/TESLA-MODEL-3-BATTERY.cpp | 11 + Software/src/devboard/utils/events.cpp | 136 ++++++++++++ Software/src/devboard/utils/events.h | 35 +++ Software/src/devboard/utils/test_commands.txt | 1 + Software/src/devboard/utils/timer.cpp | 10 +- Software/src/devboard/utils/timer.h | 4 +- test/CMakeLists.txt | 18 ++ test/microtest.h | 209 ++++++++++++++++++ test/test_lib.cpp | 6 + test/test_lib.h | 43 ++++ test/utils/events_test.cpp | 78 +++++++ test/utils/timer_test.cpp | 32 +++ 25 files changed, 657 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/unit-tests.yml create mode 100644 CMakeLists.txt create mode 100644 Software/src/devboard/utils/events.cpp create mode 100644 Software/src/devboard/utils/events.h create mode 100644 Software/src/devboard/utils/test_commands.txt create mode 100644 test/CMakeLists.txt create mode 100644 test/microtest.h create mode 100644 test/test_lib.cpp create mode 100644 test/test_lib.h create mode 100644 test/utils/events_test.cpp create mode 100644 test/utils/timer_test.cpp diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..1381259a --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,23 @@ +name: Run Unit Tests + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Configure and build with CMake + run: | + mkdir build + cd build + cmake .. + cmake --build . + + - name: Run unit tests + run: | + cd build/test + find . -type f -executable -exec {} \; diff --git a/.gitignore b/.gitignore index 65ad4290..c32db3bf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,9 @@ # Ignore any files in the build folder Software/build/ + +# Ignore CMake build folder +build/ + +# Ignore unit tests +*.exe \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..6f0c4af6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.10) + +project(BatteryEmulator) + +# add_subdirectory(Software/src/devboard/utils) +add_subdirectory(test) diff --git a/Software/Software.ino b/Software/Software.ino index 2ec566d6..eb65591a 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -8,6 +8,7 @@ #include "src/battery/BATTERIES.h" #include "src/charger/CHARGERS.h" #include "src/devboard/config.h" +#include "src/devboard/utils/events.h" #include "src/inverter/INVERTERS.h" #include "src/lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h" #include "src/lib/eModbus-eModbus/Logging.h" @@ -129,6 +130,8 @@ void setup() { init_webserver(); #endif + init_events(); + init_CAN(); init_LED(); @@ -183,6 +186,7 @@ void loop() { { previousMillisUpdateVal = millis(); update_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus. + set_event(EVENT_DUMMY, (uint8_t)millis()); } // Output @@ -190,6 +194,7 @@ void loop() { #ifdef DUAL_CAN send_can2(); #endif + update_event_timestamps(); } // Initialization functions diff --git a/Software/USER_SETTINGS.h b/Software/USER_SETTINGS.h index 0b4fe1c1..115a10a8 100644 --- a/Software/USER_SETTINGS.h +++ b/Software/USER_SETTINGS.h @@ -12,7 +12,7 @@ //#define CHADEMO_BATTERY //#define IMIEV_CZERO_ION_BATTERY //#define KIA_HYUNDAI_64_BATTERY -//#define NISSAN_LEAF_BATTERY +#define NISSAN_LEAF_BATTERY //#define RENAULT_KANGOO_BATTERY //#define RENAULT_ZOE_BATTERY //#define SANTA_FE_PHEV_BATTERY @@ -21,7 +21,7 @@ /* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */ //#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus -//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU +#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU //#define LUNA2000_MODBUS //Enable this line to emulate a "Luna2000 battery" over Modbus RTU //#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus //#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus diff --git a/Software/src/battery/BMW-I3-BATTERY.cpp b/Software/src/battery/BMW-I3-BATTERY.cpp index 935da03e..40acb0e2 100644 --- a/Software/src/battery/BMW-I3-BATTERY.cpp +++ b/Software/src/battery/BMW-I3-BATTERY.cpp @@ -1,4 +1,5 @@ #include "BMW-I3-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -103,6 +104,7 @@ void update_values_i3_battery() { //This function maps all the values fetched v if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/CHADEMO-BATTERY.cpp b/Software/src/battery/CHADEMO-BATTERY.cpp index 929c0a15..84a3634d 100644 --- a/Software/src/battery/CHADEMO-BATTERY.cpp +++ b/Software/src/battery/CHADEMO-BATTERY.cpp @@ -1,4 +1,5 @@ #include "CHADEMO-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -107,6 +108,7 @@ void update_values_chademo_battery() { //This function maps all the values fetc bms_status = FAULT; errorCode = 7; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp index f9a4d97d..51ebdba9 100644 --- a/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp +++ b/Software/src/battery/IMIEV-CZERO-ION-BATTERY.cpp @@ -1,4 +1,5 @@ #include "IMIEV-CZERO-ION-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -109,6 +110,7 @@ void update_values_imiev_battery() { //This function maps all the values fetche if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp index 40c948cd..e1b27df7 100644 --- a/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp +++ b/Software/src/battery/KIA-HYUNDAI-64-BATTERY.cpp @@ -1,4 +1,5 @@ #include "KIA-HYUNDAI-64-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -200,6 +201,7 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -207,11 +209,13 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (waterleakageSensor == 0) { Serial.println("Water leakage inside battery detected. Operation halted. Inspect battery!"); bms_status = FAULT; + set_event(EVENT_WATER_INGRESS, 0); } if (leadAcidBatteryVoltage < 110) { Serial.println("12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"); LEDcolor = YELLOW; + set_event(EVENT_12V_LOW, leadAcidBatteryVoltage); } // Check if cell voltages are within allowed range @@ -220,14 +224,17 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value if (cell_max_voltage >= MAX_CELL_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (cell_min_voltage <= MIN_CELL_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } if (bms_status == FAULT) { //Incase we enter a critical fault state, zero out the allowed limits diff --git a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp index 17a28679..835487ee 100644 --- a/Software/src/battery/NISSAN-LEAF-BATTERY.cpp +++ b/Software/src/battery/NISSAN-LEAF-BATTERY.cpp @@ -2,6 +2,7 @@ #ifdef MQTT #include "../devboard/mqtt/mqtt.h" #endif +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -262,6 +263,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!"); #endif + set_event(EVENT_SOC_PLAUSIBILITY_ERROR, LB_SOC / 10); } } @@ -311,6 +313,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"); #endif + set_event(EVENT_BATTERY_DISCHG_STOP_REQ, 0); break; case (6): //Caution Lamp Request & Charging Mode Stop Request @@ -319,6 +322,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"); #endif + set_event(EVENT_BATTERY_CHG_STOP_REQ, 0); break; case (7): //Caution Lamp Request & Charging Mode Stop Request & Normal Stop Request @@ -328,6 +332,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched Serial.println( "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"); #endif + set_event(EVENT_BATTERY_CHG_DISCHG_STOP_REQ, 0); break; default: break; @@ -342,6 +347,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #endif bms_status = FAULT; errorCode = 5; + set_event(EVENT_LOW_SOH, LB_StateOfHealth); max_target_discharge_power = 0; max_target_charge_power = 0; } @@ -355,6 +361,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched "disabled!"); #endif bms_status = FAULT; + set_event(EVENT_HVIL_FAILURE, 0); errorCode = 6; SOC = 0; max_target_discharge_power = 0; @@ -369,6 +376,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control."); #endif + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -380,6 +388,7 @@ void update_values_leaf_battery() { /* This function maps all the values fetched #ifdef DEBUG_VIA_USB Serial.println("ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"); #endif + set_event(EVENT_CAN_WARNING, 0); } /*Finally print out values to serial if configured to do so*/ @@ -615,6 +624,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("HIGH CELL DEVIATION!!! Inspect battery!"); #endif + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } if (min_max_voltage[1] >= MAX_CELL_VOLTAGE) { @@ -623,6 +633,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); #endif + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (min_max_voltage[0] <= MIN_CELL_VOLTAGE) { bms_status = FAULT; @@ -630,6 +641,7 @@ void receive_can_leaf_battery(CAN_frame_t rx_frame) { #ifdef DEBUG_VIA_USB Serial.println("CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); #endif + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } break; } diff --git a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp index 4f818878..47ab1f64 100644 --- a/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp +++ b/Software/src/battery/RENAULT-KANGOO-BATTERY.cpp @@ -1,4 +1,5 @@ #include "RENAULT-KANGOO-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -126,6 +127,7 @@ void update_values_kangoo_battery() { //This function maps all the values fetch if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -133,14 +135,17 @@ void update_values_kangoo_battery() { //This function maps all the values fetch if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL mV DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } #ifdef DEBUG_VIA_USB diff --git a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp index b65b71ec..d40f09cf 100644 --- a/Software/src/battery/RENAULT-ZOE-BATTERY.cpp +++ b/Software/src/battery/RENAULT-ZOE-BATTERY.cpp @@ -1,4 +1,5 @@ #include "RENAULT-ZOE-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -87,6 +88,7 @@ void update_values_zoe_battery() { //This function maps all the values fetched if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } @@ -94,14 +96,17 @@ void update_values_zoe_battery() { //This function maps all the values fetched if (LB_Cell_Max_Voltage >= ABSOLUTE_CELL_MAX_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (LB_Cell_Min_Voltage <= ABSOLUTE_CELL_MIN_VOLTAGE) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_MV) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL mV DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } #ifdef DEBUG_VIA_USB diff --git a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp index 2959a684..e2aaa66f 100644 --- a/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp +++ b/Software/src/battery/SANTA-FE-PHEV-BATTERY.cpp @@ -1,4 +1,5 @@ #include "SANTA-FE-PHEV-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -84,6 +85,7 @@ void update_values_santafe_phev_battery() { //This function maps all the values if (!CANstillAlive) { bms_status = FAULT; Serial.println("No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { CANstillAlive--; } diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index 59aa7c18..fb05806a 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -1,4 +1,5 @@ #include "TESLA-MODEL-3-BATTERY.h" +#include "../devboard/utils/events.h" #include "../lib/miwagner-ESP32-Arduino-CAN/CAN_config.h" #include "../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" @@ -231,6 +232,7 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (!stillAliveCAN) { bms_status = FAULT; Serial.println("ERROR: No CAN communication detected for 60s. Shutting down battery control."); + set_event(EVENT_CAN_FAILURE, 0); } else { stillAliveCAN--; } @@ -238,6 +240,7 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (hvil_status == 3) { //INTERNAL_OPEN_FAULT - Someone disconnected a high voltage cable while battery was in use bms_status = FAULT; Serial.println("ERROR: High voltage cable removed while battery running. Opening contactors!"); + set_event(EVENT_INTERNAL_OPEN_FAULT, 0); } cell_deviation_mV = (cell_max_v - cell_min_v); @@ -258,12 +261,14 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (SOC < 6500) { //When SOC is less than 65.00% when approaching max voltage bms_status = FAULT; Serial.println("ERROR: SOC% reported by battery not plausible. Restart battery!"); + set_event(EVENT_SOC_PLAUSIBILITY_ERROR, SOC / 100); } } //Check if BMS is in need of recalibration if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) { Serial.println("Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); + set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy); LEDcolor = YELLOW; } @@ -271,27 +276,33 @@ void update_values_tesla_model_3_battery() { //This function maps all the value if (cell_max_v >= MAX_CELL_VOLTAGE_LFP) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (cell_min_v <= MIN_CELL_VOLTAGE_LFP) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_LFP) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } } else { //NCA/NCM limits used if (cell_max_v >= MAX_CELL_VOLTAGE_NCA_NCM) { bms_status = FAULT; Serial.println("ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_OVER_VOLTAGE, 0); } if (cell_min_v <= MIN_CELL_VOLTAGE_NCA_NCM) { bms_status = FAULT; Serial.println("ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + set_event(EVENT_CELL_UNDER_VOLTAGE, 0); } if (cell_deviation_mV > MAX_CELL_DEVIATION_NCA_NCM) { LEDcolor = YELLOW; Serial.println("ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + set_event(EVENT_CELL_DEVIATION_HIGH, 0); } } diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp new file mode 100644 index 00000000..06963830 --- /dev/null +++ b/Software/src/devboard/utils/events.cpp @@ -0,0 +1,136 @@ +#include "events.h" + +#include "../../../USER_SETTINGS.h" +#include "../config.h" + +typedef struct { + uint32_t timestamp; // Time in seconds since startup when the event occurred + uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage + uint8_t occurences; // Number of occurrences since startup + uint8_t led_color; // Weirdly indented comment +} EVENTS_STRUCT_TYPE; + +static EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; +static unsigned long previous_millis = 0; +static uint32_t time_seconds = 0; +static uint8_t total_led_color = GREEN; +static char event_message[256]; + +/* Local function prototypes */ +static void set_event_message(EVENTS_ENUM_TYPE event); +static void update_led_color(EVENTS_ENUM_TYPE event); + +/* Exported functions */ +void init_events(void) { + for (uint8_t i = 0; i < EVENT_NOF_EVENTS; i++) { + entries[i].timestamp = 0; + entries[i].data = 0; + entries[i].occurences = 0; + entries[i].led_color = RED; // Most events are RED + } + + // YELLOW events below + entries[EVENT_12V_LOW].led_color = YELLOW; + entries[EVENT_CAN_WARNING].led_color = YELLOW; + entries[EVENT_CELL_DEVIATION_HIGH].led_color = YELLOW; + entries[EVENT_KWH_PLAUSIBILITY_ERROR].led_color = YELLOW; +} + +void set_event(EVENTS_ENUM_TYPE event, uint8_t data) { + if (event >= EVENT_NOF_EVENTS) { + event = EVENT_UNKNOWN_EVENT_SET; + } + entries[event].timestamp = time_seconds; + entries[event].data = data; + entries[event].occurences++; + set_event_message(event); +#ifdef DEBUG_VIA_USB + Serial.println(event_message); +#endif +} + +void update_event_timestamps(void) { + unsigned long new_millis = millis(); + if (new_millis - previous_millis >= 1000) { + time_seconds++; + previous_millis = new_millis; + } +} + +/* Local functions */ +static void update_led_color(EVENTS_ENUM_TYPE event) { + total_led_color = (total_led_color == RED) ? RED : entries[event].led_color; +} + +static void set_event_message(EVENTS_ENUM_TYPE event) { + switch (event) { + case EVENT_CAN_FAILURE: + snprintf(event_message, sizeof(event_message), + "No CAN communication detected for 60s. Shutting down battery control."); + break; + case EVENT_CAN_WARNING: + snprintf(event_message, sizeof(event_message), + "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"); + break; + case EVENT_WATER_INGRESS: + snprintf(event_message, sizeof(event_message), + "Water leakage inside battery detected. Operation halted. Inspect battery!"); + break; + case EVENT_12V_LOW: + snprintf(event_message, sizeof(event_message), + "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"); + break; + case EVENT_SOC_PLAUSIBILITY_ERROR: + snprintf(event_message, sizeof(event_message), "ERROR: SOC% reported by battery not plausible. Restart battery!"); + break; + case EVENT_KWH_PLAUSIBILITY_ERROR: + snprintf(event_message, sizeof(event_message), + "Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); + break; + case EVENT_BATTERY_CHG_STOP_REQ: + snprintf(event_message, sizeof(event_message), + "ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"); + break; + case EVENT_BATTERY_DISCHG_STOP_REQ: + snprintf(event_message, sizeof(event_message), + "ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"); + break; + case EVENT_BATTERY_CHG_DISCHG_STOP_REQ: + snprintf(event_message, sizeof(event_message), + "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"); + break; + case EVENT_LOW_SOH: + snprintf( + event_message, sizeof(event_message), + "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery."); + break; + case EVENT_HVIL_FAILURE: + snprintf(event_message, sizeof(event_message), + "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be " + "disabled!"); + break; + case EVENT_INTERNAL_OPEN_FAULT: + snprintf(event_message, sizeof(event_message), + "ERROR: High voltage cable removed while battery running. Opening contactors!"); + break; + case EVENT_CELL_UNDER_VOLTAGE: + snprintf(event_message, sizeof(event_message), + "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + break; + case EVENT_CELL_OVER_VOLTAGE: + snprintf(event_message, sizeof(event_message), + "ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); + break; + case EVENT_CELL_DEVIATION_HIGH: + snprintf(event_message, sizeof(event_message), "ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); + break; + case EVENT_UNKNOWN_EVENT_SET: + snprintf(event_message, sizeof(event_message), "An unknown event was set! Review your code!"); + break; + case EVENT_DUMMY: + snprintf(event_message, sizeof(event_message), "The dummy event was set!"); // Don't change this event message! + break; + default: + break; + } +} diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h new file mode 100644 index 00000000..58abb58e --- /dev/null +++ b/Software/src/devboard/utils/events.h @@ -0,0 +1,35 @@ +#ifndef __EVENTS_H__ +#define __EVENTS_H__ + +#ifndef UNIT_TEST +#include +#endif + +#include + +typedef enum { + EVENT_CAN_FAILURE = 0u, + EVENT_CAN_WARNING, + EVENT_WATER_INGRESS, + EVENT_12V_LOW, + EVENT_SOC_PLAUSIBILITY_ERROR, + EVENT_KWH_PLAUSIBILITY_ERROR, + EVENT_BATTERY_CHG_STOP_REQ, + EVENT_BATTERY_DISCHG_STOP_REQ, + EVENT_BATTERY_CHG_DISCHG_STOP_REQ, + EVENT_LOW_SOH, + EVENT_HVIL_FAILURE, + EVENT_INTERNAL_OPEN_FAULT, + EVENT_CELL_UNDER_VOLTAGE, + EVENT_CELL_OVER_VOLTAGE, + EVENT_CELL_DEVIATION_HIGH, + EVENT_UNKNOWN_EVENT_SET, + EVENT_DUMMY, + EVENT_NOF_EVENTS +} EVENTS_ENUM_TYPE; + +void init_events(void); +void set_event(EVENTS_ENUM_TYPE event, uint8_t data); +void update_event_timestamps(void); + +#endif // __MYTIMER_H__ diff --git a/Software/src/devboard/utils/test_commands.txt b/Software/src/devboard/utils/test_commands.txt new file mode 100644 index 00000000..af3b68f6 --- /dev/null +++ b/Software/src/devboard/utils/test_commands.txt @@ -0,0 +1 @@ +g++ events_test.cpp ../../../test/test_lib.cpp -o events_test.exe -DUNIT_TEST -I. \ No newline at end of file diff --git a/Software/src/devboard/utils/timer.cpp b/Software/src/devboard/utils/timer.cpp index 5b4d59bf..ff051a03 100644 --- a/Software/src/devboard/utils/timer.cpp +++ b/Software/src/devboard/utils/timer.cpp @@ -1,11 +1,13 @@ #include "timer.h" -MyTimer::MyTimer(unsigned long interval) : interval(interval), previousMillis(0) {} +MyTimer::MyTimer(unsigned long interval) : interval(interval) { + previous_millis = millis(); +} bool MyTimer::elapsed() { - unsigned long currentMillis = millis(); - if (currentMillis - previousMillis >= interval) { - previousMillis = currentMillis; + unsigned long current_millis = millis(); + if (current_millis - previous_millis >= interval) { + previous_millis = current_millis; return true; } return false; diff --git a/Software/src/devboard/utils/timer.h b/Software/src/devboard/utils/timer.h index 829ea1e5..e330eb2f 100644 --- a/Software/src/devboard/utils/timer.h +++ b/Software/src/devboard/utils/timer.h @@ -1,7 +1,9 @@ #ifndef __MYTIMER_H__ #define __MYTIMER_H__ +#ifndef UNIT_TEST #include +#endif class MyTimer { public: @@ -12,7 +14,7 @@ class MyTimer { private: unsigned long interval; - unsigned long previousMillis; + unsigned long previous_millis; }; #endif // __MYTIMER_H__ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..6d63e2e3 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,18 @@ +# Include the directory with your source files +include_directories(${CMAKE_SOURCE_DIR}/Software/src/devboard/utils .) + +# Create a variable to store the list of test files +file(GLOB TEST_SOURCES utils/*.cpp) + +# Loop through each test source file and create an executable +foreach(TEST_SOURCE ${TEST_SOURCES}) + # Extract the test name without extension + get_filename_component(TEST_NAME ${TEST_SOURCE} NAME_WE) + + # Create an executable for the test + add_executable(${TEST_NAME} ${TEST_SOURCE} test_lib.cpp) + + # Apply the target_compile_definitions for the test + target_compile_definitions(${TEST_NAME} PRIVATE UNIT_TEST) + +endforeach() diff --git a/test/microtest.h b/test/microtest.h new file mode 100644 index 00000000..36c73aa1 --- /dev/null +++ b/test/microtest.h @@ -0,0 +1,209 @@ +// +// microtest.h +// +// URL: https://github.com/torpedro/microtest.h +// Author: Pedro Flemming (http://torpedro.com/) +// License: MIT License (https://github.com/torpedro/microtest.h/blob/master/LICENSE) +// Copyright (c) 2017 Pedro Flemming +// +// This is a small header-only C++ unit testing framework. +// It allows to define small unit tests with set of assertions available. +// +#ifndef __MICROTEST_H__ +#define __MICROTEST_H__ + +#include +#include +#include +#include + +//////////////// +// Assertions // +//////////////// + +#define ASSERT(cond) ASSERT_TRUE(cond); + +#define ASSERT_TRUE(cond) \ + if (!(cond)) \ + throw mt::AssertFailedException(#cond, __FILE__, __LINE__); + +#define ASSERT_FALSE(cond) \ + if (cond) \ + throw mt::AssertFailedException(#cond, __FILE__, __LINE__); + +#define ASSERT_NULL(value) ASSERT_TRUE(value == NULL); + +#define ASSERT_NOTNULL(value) ASSERT_TRUE(value != NULL); + +#define ASSERT_STREQ(a, b) \ + if (std::string(a).compare(std::string(b)) != 0) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " != " << b << std::endl; \ + throw mt::AssertFailedException(#a " == " #b, __FILE__, __LINE__); \ + } + +#define ASSERT_STRNEQ(a, b) \ + if (std::string(a).compare(std::string(b)) != = 0) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " == " << b << std::endl; \ + throw mt::AssertFailedException(#a " != " #b, __FILE__, __LINE__); \ + } + +#define ASSERT_EQ(a, b) \ + if (a != b) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " != " << b << std::endl; \ + } \ + ASSERT(a == b); + +#define ASSERT_NEQ(a, b) \ + if (a == b) { \ + printf("%s{ info} %s", mt::yellow(), mt::def()); \ + std::cout << "Actual values: " << a << " == " << b << std::endl; \ + } \ + ASSERT(a != b); + +//////////////// +// Unit Tests // +//////////////// + +#define TEST(name) \ + void name(); \ + namespace { \ + bool __##name = mt::TestsManager::AddTest(name, #name); \ + } \ + void name() + +/////////////// +// Framework // +/////////////// + +namespace mt { + +inline const char* red() { + return "\033[1;31m"; +} + +inline const char* green() { + return "\033[0;32m"; +} + +inline const char* yellow() { + return "\033[0;33m"; +} + +inline const char* def() { + return "\033[0m"; +} + +inline void printRunning(const char* message, FILE* file = stdout) { + fprintf(file, "%s{ running}%s %s\n", green(), def(), message); +} + +inline void printOk(const char* message, FILE* file = stdout) { + fprintf(file, "%s{ ok}%s %s\n", green(), def(), message); +} + +inline void printFailed(const char* message, FILE* file = stdout) { + fprintf(file, "%s{ failed} %s%s\n", red(), message, def()); +} + +// Exception that is thrown when an assertion fails. +class AssertFailedException : public std::exception { + public: + AssertFailedException(std::string description, std::string filepath, int line) + : std::exception(), description_(description), filepath_(filepath), line_(line){}; + + virtual const char* what() const throw() { return description_.c_str(); } + + inline const char* getFilepath() { return filepath_.c_str(); } + + inline int getLine() { return line_; } + + protected: + std::string description_; + std::string filepath_; + int line_; +}; + +class TestsManager { + // Note: static initialization fiasco + // http://www.parashift.com/c++-faq-lite/static-init-order.html + // http://www.parashift.com/c++-faq-lite/static-init-order-on-first-use.html + public: + struct Test { + const char* name; + void (*fn)(void); + }; + + static std::vector& tests() { + static std::vector tests_; + return tests_; + } + + // Adds a new test to the current set of tests. + // Returns false if a test with the same name already exists. + inline static bool AddTest(void (*fn)(void), const char* name) { + tests().push_back({name, fn}); + return true; + } + + // Run all tests that are registered. + // Returns the number of tests that failed. + inline static size_t RunAllTests(FILE* file = stdout) { + size_t num_failed = 0; + + for (const Test& test : tests()) { + // Run the test. + // If an AsserFailedException is thrown, the test has failed. + try { + printRunning(test.name, file); + + (*test.fn)(); + + printOk(test.name, file); + + } catch (AssertFailedException& e) { + printFailed(test.name, file); + fprintf(file, " %sAssertion failed: %s%s\n", red(), e.what(), def()); + fprintf(file, " %s%s:%d%s\n", red(), e.getFilepath(), e.getLine(), def()); + ++num_failed; + } + } + + int return_code = (num_failed > 0) ? 1 : 0; + return return_code; + } +}; + +// Class that will capture the arguments passed to the program. +class Runtime { + public: + static const std::vector& args(int argc = -1, char** argv = NULL) { + static std::vector args_; + if (argc >= 0) { + for (int i = 0; i < argc; ++i) { + args_.push_back(argv[i]); + } + } + return args_; + } +}; +} // namespace mt + +#define TEST_MAIN() \ + int main(int argc, char* argv[]) { \ + mt::Runtime::args(argc, argv); \ + \ + size_t num_failed = mt::TestsManager::RunAllTests(stdout); \ + if (num_failed == 0) { \ + fprintf(stdout, "%s{ summary} All tests succeeded!%s\n", mt::green(), mt::def()); \ + return 0; \ + } else { \ + double percentage = 100.0 * num_failed / mt::TestsManager::tests().size(); \ + fprintf(stderr, "%s{ summary} %lu tests failed (%.2f%%)%s\n", mt::red(), num_failed, percentage, mt::def()); \ + return -1; \ + } \ + } + +#endif // __MICROTEST_H__ diff --git a/test/test_lib.cpp b/test/test_lib.cpp new file mode 100644 index 00000000..8ed9c2f5 --- /dev/null +++ b/test/test_lib.cpp @@ -0,0 +1,6 @@ +#include "test_lib.h" +#include + +MySerial Serial; + +unsigned long testlib_millis = 0; diff --git a/test/test_lib.h b/test/test_lib.h new file mode 100644 index 00000000..ccca3ca4 --- /dev/null +++ b/test/test_lib.h @@ -0,0 +1,43 @@ +#ifndef __TEST_LIB_H__ +#define __TEST_LIB_H__ + +#include +#include +#include + +#include "microtest.h" + +class MySerial; + +extern unsigned long testlib_millis; + +/* Mock millis() */ +static inline unsigned long millis(void) { + return testlib_millis; +} + +/* Mock Serial class */ +class MySerial { + public: + size_t println(const char* s) { + return print(s, true); // Call print with newline argument true + } + + size_t print(const char* s) { + return print(s, false); // Call print with newline argument false + } + + private: + size_t print(const char* s, bool newline) { + size_t length = printf("%s", s); // Print the string without newline + if (newline) { + printf("\n"); // Add a newline if specified + length++; // Increment length to account for the added newline character + } + return length; // Return the total length printed + } +}; + +extern MySerial Serial; + +#endif diff --git a/test/utils/events_test.cpp b/test/utils/events_test.cpp new file mode 100644 index 00000000..1d6c0748 --- /dev/null +++ b/test/utils/events_test.cpp @@ -0,0 +1,78 @@ +// The test library must be included first! +#include "test_lib.h" + +#include "events.cpp" + +/* Helper functions */ +static void reset_event_msg(void) { + snprintf(event_message, sizeof(event_message), ""); +} + +TEST(init_events_test) { + init_events(); + + for (uint8_t i = 0; i < EVENT_NOF_EVENTS; i++) { + ASSERT_EQ(entries[i].occurences, 0); + } +} + +TEST(update_event_timestamps_test) { + // Reset + init_events(); + time_seconds = 0; + + // No delta, so time shouldn't increase + testlib_millis = 0; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 0); + + // Almost time to bump the seconds + testlib_millis = 999; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 0); + ASSERT_EQ(previous_millis, 0); + + // millis == 1000, so we should add a second + testlib_millis = 1000; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 1); + ASSERT_EQ(previous_millis, 1000); + + // We shouldn't add more seconds until 2000 now + testlib_millis = 1999; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 1); + ASSERT_EQ(previous_millis, 1000); + testlib_millis = 2000; + update_event_timestamps(); + ASSERT_EQ(time_seconds, 2); + ASSERT_EQ(previous_millis, 2000); +} + +TEST(set_event_test) { + // Reset + init_events(); + time_seconds = 0; + + // Initially, the event should not have any data or occurences + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].data, 0); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].occurences, 0); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 0); + // Set current time and overvoltage event for cell 23 + time_seconds = 345; + set_event(EVENT_CELL_OVER_VOLTAGE, 123); + // Ensure proper event data + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].data, 123); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].occurences, 1); + ASSERT_EQ(entries[EVENT_CELL_OVER_VOLTAGE].timestamp, 345); +} + +TEST(event_message_test) { + reset_event_msg(); + + set_event(EVENT_DUMMY, 0); // Set dummy event with no data + + ASSERT_STREQ("The dummy event was set!", event_message); +} + +TEST_MAIN(); diff --git a/test/utils/timer_test.cpp b/test/utils/timer_test.cpp new file mode 100644 index 00000000..96e9b142 --- /dev/null +++ b/test/utils/timer_test.cpp @@ -0,0 +1,32 @@ +// The test library must be included first! +#include "../test_lib.h" + +#include "timer.cpp" + +/* Helper functions */ + +/* Test functions */ + +TEST(timer_test) { + unsigned long test_interval = 10; + + testlib_millis = 0; + MyTimer timer(test_interval); + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = test_interval - 1; + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = test_interval; + ASSERT_EQ(timer.elapsed(), true); + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = 2 * test_interval - 1; + ASSERT_EQ(timer.elapsed(), false); + + testlib_millis = 2 * test_interval; + ASSERT_EQ(timer.elapsed(), true); + ASSERT_EQ(timer.elapsed(), false); +} + +TEST_MAIN(); From 89a8a947affd8207999c5096640a4db818287d86 Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Wed, 7 Feb 2024 06:35:55 +1100 Subject: [PATCH 24/44] Revert "Merge pull request #153 from kharnt0x/wifi-monitor" This reverts commit 26cbc47c155001f904e0274e88755b52982605ee, reversing changes made to 38524e27d1070423ae06e187e111141b33162e4d. --- Software/Software.ino | 1 - Software/src/devboard/webserver/webserver.cpp | 78 ++++++------------- Software/src/devboard/webserver/webserver.h | 9 --- 3 files changed, 25 insertions(+), 63 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index eb65591a..11ee8e6a 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -157,7 +157,6 @@ void loop() { #ifdef WEBSERVER // Over-the-air updates by ElegantOTA ElegantOTA.loop(); - WiFi_monitor_loop(); #ifdef MQTT mqtt_loop(); #endif diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 039257a6..d3858420 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -42,9 +42,7 @@ bool wifi_connected; // Wifi connect time declarations and definition unsigned long wifi_connect_start_time; unsigned long wifi_connect_current_time; -unsigned long wifi_connect_timeout = 5000; // Timeout for WiFi connect in milliseconds -unsigned long wifi_monitor_loop_time = 30000; // Will check if WiFi is connected and try reconnect every x milliseconds -unsigned long last_wifi_monitor_run = 0; +const long wifi_connect_timeout = 5000; // Timeout for WiFi connect in milliseconds void init_webserver() { // Configure WiFi @@ -249,23 +247,6 @@ void init_webserver() { #endif } -void WiFi_monitor_loop() { - unsigned long currentMillis = millis(); - if (currentMillis - last_wifi_monitor_run > wifi_monitor_loop_time) { - last_wifi_monitor_run = currentMillis; - if (WiFi.status() != WL_CONNECTED && wifi_state != "Connecting") { - wifi_connected = false; - wifi_state = "Not connected"; - Serial.print("Wifi disconnected. Attempting reconnection"); - init_WiFi_STA(ssid, password); - } else if (WiFi.status() == WL_CONNECTED && wifi_state != "Connected") { - wifi_connected = true; - wifi_state = "Connected"; - Serial.println("Wifi reconnected"); - } - } -} - void init_WiFi_AP() { Serial.print("Creating Access Point: "); Serial.println(ssidAP); @@ -281,45 +262,36 @@ void init_WiFi_AP() { } void init_WiFi_STA(const char* ssid, const char* password) { - // If we're already connected, there's nothing to do - if (WiFi.status() == WL_CONNECTED) { - if (wifi_state != "Connected") { - wifi_connected = true; - wifi_state = "Connected"; - // Print local IP address and start web server - Serial.println(""); - Serial.print("Connected to WiFi network: "); - Serial.println(ssid); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - } - return; + // Connect to Wi-Fi network with SSID and password + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.begin(ssid, password); + + wifi_connect_start_time = millis(); + wifi_connect_current_time = wifi_connect_start_time; + while ((wifi_connect_current_time - wifi_connect_start_time) <= wifi_connect_timeout && + WiFi.status() != WL_CONNECTED) { // do this loop for up to 5000ms + // to break the loop when the connection is not established (wrong ssid or password). + delay(500); + Serial.print("."); + wifi_connect_current_time = millis(); } - - // If we're not currently trying to connect, start the connection process - if (wifi_state != "Connecting") { - Serial.print("Connecting to: "); + if (WiFi.status() == WL_CONNECTED) { // WL_CONNECTED is assigned when connected to a WiFi network + wifi_connected = true; + wifi_state = "Connected"; + // Print local IP address and start web server + Serial.println(""); + Serial.print("Connected to WiFi network: "); Serial.println(ssid); - WiFi.begin(ssid, password); - wifi_state = "Connecting"; - wifi_connect_start_time = millis(); - return; - } - - // If we've been trying to connect for more than 5000ms, give up - if (millis() - wifi_connect_start_time > wifi_connect_timeout) { + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } else { + wifi_connected = false; wifi_state = "Not connected"; - Serial.print("Failed to connect to WiFi network: "); + Serial.print("Not connected to WiFi network: "); Serial.println(ssid); Serial.println("Please check WiFi network name and password, and if WiFi network is available."); - Serial.print("Will try again in "); - Serial.print((wifi_monitor_loop_time - (millis() - last_wifi_monitor_run)) / 1000); - Serial.println(" seconds."); - return; } - - // Otherwise, just print a dot to indicate that we're still trying to connect - Serial.print("."); } void init_ElegantOTA() { diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index f9043c6f..dac3d031 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -81,15 +81,6 @@ void init_WiFi_AP(); */ void init_WiFi_STA(const char* ssid, const char* password); -/** - * @brief Monitoring loop for WiFi. Will attempt to reconnect to access point if the connection goes down. - * - * @param[in] void - * - * @return void - */ -void WiFi_monitor_loop(); - /** * @brief Initialization function for ElegantOTA. * From 3503b8586aeacbdf2a369eb39df62b5bc4d2d852 Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Wed, 7 Feb 2024 07:52:44 +1100 Subject: [PATCH 25/44] add event log to webserver --- Software/src/devboard/utils/events.cpp | 87 ++++++------------- Software/src/devboard/utils/events.h | 57 ++++++++---- Software/src/devboard/webserver/webserver.cpp | 48 ++++++++++ Software/src/devboard/webserver/webserver.h | 11 +++ 4 files changed, 126 insertions(+), 77 deletions(-) diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 06963830..35a8d256 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -3,14 +3,6 @@ #include "../../../USER_SETTINGS.h" #include "../config.h" -typedef struct { - uint32_t timestamp; // Time in seconds since startup when the event occurred - uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage - uint8_t occurences; // Number of occurrences since startup - uint8_t led_color; // Weirdly indented comment -} EVENTS_STRUCT_TYPE; - -static EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; static unsigned long previous_millis = 0; static uint32_t time_seconds = 0; static uint8_t total_led_color = GREEN; @@ -62,75 +54,52 @@ static void update_led_color(EVENTS_ENUM_TYPE event) { total_led_color = (total_led_color == RED) ? RED : entries[event].led_color; } -static void set_event_message(EVENTS_ENUM_TYPE event) { +const char* get_event_message(EVENTS_ENUM_TYPE event) { switch (event) { case EVENT_CAN_FAILURE: - snprintf(event_message, sizeof(event_message), - "No CAN communication detected for 60s. Shutting down battery control."); - break; + return "No CAN communication detected for 60s. Shutting down battery control."; case EVENT_CAN_WARNING: - snprintf(event_message, sizeof(event_message), - "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"); - break; + return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!"; case EVENT_WATER_INGRESS: - snprintf(event_message, sizeof(event_message), - "Water leakage inside battery detected. Operation halted. Inspect battery!"); - break; + return "Water leakage inside battery detected. Operation halted. Inspect battery!"; case EVENT_12V_LOW: - snprintf(event_message, sizeof(event_message), - "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"); - break; + return "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!"; case EVENT_SOC_PLAUSIBILITY_ERROR: - snprintf(event_message, sizeof(event_message), "ERROR: SOC% reported by battery not plausible. Restart battery!"); - break; + return "ERROR: SOC% reported by battery not plausible. Restart battery!"; case EVENT_KWH_PLAUSIBILITY_ERROR: - snprintf(event_message, sizeof(event_message), - "Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); - break; + return "Warning: kWh remaining reported by battery not plausible. Battery needs cycling."; case EVENT_BATTERY_CHG_STOP_REQ: - snprintf(event_message, sizeof(event_message), - "ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"); - break; + return "ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!"; case EVENT_BATTERY_DISCHG_STOP_REQ: - snprintf(event_message, sizeof(event_message), - "ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"); - break; + return "ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!"; case EVENT_BATTERY_CHG_DISCHG_STOP_REQ: - snprintf(event_message, sizeof(event_message), - "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"); - break; + return "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!"; case EVENT_LOW_SOH: - snprintf( - event_message, sizeof(event_message), - "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery."); - break; + return "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle battery."; case EVENT_HVIL_FAILURE: - snprintf(event_message, sizeof(event_message), - "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be " - "disabled!"); - break; + return "ERROR: Battery interlock loop broken. Check that high voltage connectors are seated. Battery will be disabled!"; case EVENT_INTERNAL_OPEN_FAULT: - snprintf(event_message, sizeof(event_message), - "ERROR: High voltage cable removed while battery running. Opening contactors!"); - break; + return "ERROR: High voltage cable removed while battery running. Opening contactors!"; case EVENT_CELL_UNDER_VOLTAGE: - snprintf(event_message, sizeof(event_message), - "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); - break; + return "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"; case EVENT_CELL_OVER_VOLTAGE: - snprintf(event_message, sizeof(event_message), - "ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"); - break; + return "ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!"; case EVENT_CELL_DEVIATION_HIGH: - snprintf(event_message, sizeof(event_message), "ERROR: HIGH CELL DEVIATION!!! Inspect battery!"); - break; + return "ERROR: HIGH CELL DEVIATION!!! Inspect battery!"; case EVENT_UNKNOWN_EVENT_SET: - snprintf(event_message, sizeof(event_message), "An unknown event was set! Review your code!"); - break; + return "An unknown event was set! Review your code!"; case EVENT_DUMMY: - snprintf(event_message, sizeof(event_message), "The dummy event was set!"); // Don't change this event message! - break; + return "The dummy event was set!"; // Don't change this event message! default: - break; + return ""; } } + +const char* get_event_enum_string(EVENTS_ENUM_TYPE event) { + return EVENTS_ENUM_TYPE_STRING[event]; +} + +static void set_event_message(EVENTS_ENUM_TYPE event) { + const char* message = get_event_message(event); + snprintf(event_message, sizeof(event_message), "%s", message); +} diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index 58abb58e..c75fa65a 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -7,29 +7,50 @@ #include +#define EVENTS_ENUM_TYPE(XX) \ + XX(EVENT_CAN_FAILURE) \ + XX(EVENT_CAN_WARNING) \ + XX(EVENT_WATER_INGRESS) \ + XX(EVENT_12V_LOW) \ + XX(EVENT_SOC_PLAUSIBILITY_ERROR) \ + XX(EVENT_KWH_PLAUSIBILITY_ERROR) \ + XX(EVENT_BATTERY_CHG_STOP_REQ) \ + XX(EVENT_BATTERY_DISCHG_STOP_REQ) \ + XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \ + XX(EVENT_LOW_SOH) \ + XX(EVENT_HVIL_FAILURE) \ + XX(EVENT_INTERNAL_OPEN_FAULT) \ + XX(EVENT_CELL_UNDER_VOLTAGE) \ + XX(EVENT_CELL_OVER_VOLTAGE) \ + XX(EVENT_CELL_DEVIATION_HIGH) \ + XX(EVENT_UNKNOWN_EVENT_SET) \ + XX(EVENT_DUMMY) \ + XX(EVENT_NOF_EVENTS) + +#define GENERATE_ENUM(ENUM) ENUM, +#define GENERATE_STRING(STRING) #STRING, + typedef enum { - EVENT_CAN_FAILURE = 0u, - EVENT_CAN_WARNING, - EVENT_WATER_INGRESS, - EVENT_12V_LOW, - EVENT_SOC_PLAUSIBILITY_ERROR, - EVENT_KWH_PLAUSIBILITY_ERROR, - EVENT_BATTERY_CHG_STOP_REQ, - EVENT_BATTERY_DISCHG_STOP_REQ, - EVENT_BATTERY_CHG_DISCHG_STOP_REQ, - EVENT_LOW_SOH, - EVENT_HVIL_FAILURE, - EVENT_INTERNAL_OPEN_FAULT, - EVENT_CELL_UNDER_VOLTAGE, - EVENT_CELL_OVER_VOLTAGE, - EVENT_CELL_DEVIATION_HIGH, - EVENT_UNKNOWN_EVENT_SET, - EVENT_DUMMY, - EVENT_NOF_EVENTS + EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE; +static const char *EVENTS_ENUM_TYPE_STRING[] = { + EVENTS_ENUM_TYPE(GENERATE_STRING) +}; + +const char* get_event_enum_string(EVENTS_ENUM_TYPE event); + +const char* get_event_message(EVENTS_ENUM_TYPE event); + void init_events(void); 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; +static EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; #endif // __MYTIMER_H__ diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index d3858420..f1e7e4ff 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -68,6 +68,10 @@ void init_webserver() { request->send_P(200, "text/html", index_html, cellmonitor_processor); }); + server.on("events", HTTP_GET, [](AsyncWebServerRequest* request) { + request->send_P(200, "text/html", index_html, events_processor); + }); + // Route for editing Wh server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) { if (request->hasParam("value")) { @@ -581,11 +585,14 @@ String processor(const String& var) { content += " "; content += ""; content += " "; + content += ""; + content += " "; content += ""; content += ""; + return content; + } + return String(); +} + void onOTAStart() { // Log when OTA has started Serial.println("OTA update started!"); diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index dac3d031..b2bca0e8 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -12,6 +12,7 @@ #include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h" #include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h" #include "../config.h" // Needed for LED defines +#include "../utils/events.h" #ifdef MQTT #include "../mqtt/mqtt.h" #endif @@ -35,6 +36,7 @@ extern uint16_t cellvoltages[120]; //mV 0-4350 per cell extern uint8_t LEDcolor; //Enum, 0-10 extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false +extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; extern const char* ssid; extern const char* password; @@ -117,6 +119,15 @@ String settings_processor(const String& var); */ String cellmonitor_processor(const String& var); +/** + * @brief Replaces placeholder with content section in web page + * + * @param[in] var + * + * @return String + */ +String events_processor(const String& var); + /** * @brief Executes on OTA start * From 828c9e9760ed25b766fb222f26173ecae67abe04 Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Wed, 7 Feb 2024 07:52:53 +1100 Subject: [PATCH 26/44] remove wifi minder and use inbuilt --- Software/src/devboard/webserver/webserver.cpp | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index f1e7e4ff..da55647f 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -42,7 +42,7 @@ bool wifi_connected; // Wifi connect time declarations and definition unsigned long wifi_connect_start_time; unsigned long wifi_connect_current_time; -const long wifi_connect_timeout = 5000; // Timeout for WiFi connect in milliseconds +const long wifi_connect_timeout = 8000; // Timeout for WiFi connect in milliseconds void init_webserver() { // Configure WiFi @@ -265,22 +265,40 @@ void init_WiFi_AP() { Serial.println(IP); } +String getConnectResultString(wl_status_t status) { + switch (status) { + case WL_CONNECTED: + return "Connected"; + case WL_NO_SHIELD: + return "No shield"; + case WL_IDLE_STATUS: + return "Idle status"; + case WL_NO_SSID_AVAIL: + return "No SSID available"; + case WL_SCAN_COMPLETED: + return "Scan completed"; + case WL_CONNECT_FAILED: + return "Connect failed"; + case WL_CONNECTION_LOST: + return "Connection lost"; + case WL_DISCONNECTED: + return "Disconnected"; + default: + return "Unknown"; + } +} + void init_WiFi_STA(const char* ssid, const char* password) { // Connect to Wi-Fi network with SSID and password Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password); + WiFi.setAutoReconnect(true); // Enable auto reconnect - wifi_connect_start_time = millis(); - wifi_connect_current_time = wifi_connect_start_time; - while ((wifi_connect_current_time - wifi_connect_start_time) <= wifi_connect_timeout && - WiFi.status() != WL_CONNECTED) { // do this loop for up to 5000ms - // to break the loop when the connection is not established (wrong ssid or password). - delay(500); - Serial.print("."); - wifi_connect_current_time = millis(); - } - if (WiFi.status() == WL_CONNECTED) { // WL_CONNECTED is assigned when connected to a WiFi network + wl_status_t result = static_cast(WiFi.waitForConnectResult(wifi_connect_timeout)); + Serial.println(getConnectResultString(result)); + + if (result == WL_CONNECTED) { // WL_CONNECTED is assigned when connected to a WiFi network wifi_connected = true; wifi_state = "Connected"; // Print local IP address and start web server @@ -289,6 +307,7 @@ void init_WiFi_STA(const char* ssid, const char* password) { Serial.println(ssid); Serial.print("IP address: "); Serial.println(WiFi.localIP()); + Serial.println("Signal Strength: " + String(WiFi.RSSI()) + " dBm"); } else { wifi_connected = false; wifi_state = "Not connected"; @@ -341,10 +360,11 @@ String processor(const String& var) { default: break; } + wl_status_t status = WiFi.status(); // Display ssid of network connected to and, if connected to the WiFi, its own IP content += "

SSID: " + String(ssid) + "

"; - content += "

Wifi status: " + wifi_state + "

"; - if (wifi_connected == true) { + content += "

Wifi status: " + getConnectResultString(status) + "

"; + if (status == WL_CONNECTED) { content += "

IP: " + WiFi.localIP().toString() + "

"; // Get and display the signal strength (RSSI) content += "

Signal Strength: " + String(WiFi.RSSI()) + " dBm

"; From 7d208c3ebc48a7b0ba99a5ac087c4ceb7036d4cd Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Fri, 9 Feb 2024 07:59:29 +1100 Subject: [PATCH 27/44] really want to make this thing work hey --- Software/Software.ino | 1 + Software/src/devboard/webserver/webserver.cpp | 79 +++++++++++-------- Software/src/devboard/webserver/webserver.h | 12 ++- 3 files changed, 60 insertions(+), 32 deletions(-) diff --git a/Software/Software.ino b/Software/Software.ino index 11ee8e6a..b5a56218 100644 --- a/Software/Software.ino +++ b/Software/Software.ino @@ -156,6 +156,7 @@ void loop() { #ifdef WEBSERVER // Over-the-air updates by ElegantOTA + wifi_monitor(); ElegantOTA.loop(); #ifdef MQTT mqtt_loop(); diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index da55647f..a5fe7ebc 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -36,24 +36,31 @@ const char index_html[] PROGMEM = R"rawliteral( )rawliteral"; -String wifi_state; -bool wifi_connected; - -// Wifi connect time declarations and definition -unsigned long wifi_connect_start_time; -unsigned long wifi_connect_current_time; -const long wifi_connect_timeout = 8000; // Timeout for WiFi connect in milliseconds +enum WifiState { + INIT, + RECONNECTING, + CONNECTED +}; + +WifiState wifi_state = INIT; + +unsigned const long wifi_monitor_loop_time = 15000; +unsigned long last_wifi_monitor_time = millis(); //init millis so wifi monitor doesn't run immediately +unsigned const long wifi_connect_timeout = 8000; // Timeout for WiFi connect in milliseconds +unsigned const long DEFAULT_WIFI_RECONNECT_INTERVAL = 1000; // Start with a 1 second retry interval +unsigned long wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL; +unsigned const long max_retry_interval = 30000; // Maximum retry interval of 1 minute +unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor doesn't run immediately void init_webserver() { // Configure WiFi if (AccessPointEnabled) { WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection init_WiFi_AP(); - init_WiFi_STA(ssid, password); } else { WiFi.mode(WIFI_STA); // Only Router connection - init_WiFi_STA(ssid, password); } + init_WiFi_STA(ssid, password, channel); // Route for root / web page server.on("/", HTTP_GET, @@ -288,33 +295,43 @@ String getConnectResultString(wl_status_t status) { } } -void init_WiFi_STA(const char* ssid, const char* password) { +void wifi_monitor() { + unsigned long currentMillis = millis(); + if(currentMillis - last_wifi_monitor_time > wifi_monitor_loop_time) { + last_wifi_monitor_time = currentMillis; + wl_status_t status = WiFi.status(); + if (status != WL_CONNECTED && status != WL_IDLE_STATUS) { + Serial.println(getConnectResultString(status)); + if(wifi_state == INIT) { //we haven't been connected yet, try the init logic + init_WiFi_STA(ssid, password, channel); + } else { //we were connected before, try the reconnect logic + if (currentMillis - last_wifi_attempt_time > wifi_reconnect_interval) { + last_wifi_attempt_time = currentMillis; + Serial.println("WiFi not connected, trying to reconnect..."); + wifi_state = RECONNECTING; + WiFi.reconnect(); + wifi_reconnect_interval = min(wifi_reconnect_interval * 2, max_retry_interval); + } + } + } else if (status == WL_CONNECTED && wifi_state != CONNECTED) { + wifi_state = CONNECTED; + wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL; + // Print local IP address and start web server + Serial.print("Connected to WiFi network: " + String(ssid)); + Serial.print("IP address: " + WiFi.localIP().toString()); + Serial.print("Signal Strength: " + String(WiFi.RSSI()) + " dBm "); + Serial.println("Channel: " + String(WiFi.channel())); + } + } +} + +void init_WiFi_STA(const char* ssid, const char* password, const uint8_t channel) { // Connect to Wi-Fi network with SSID and password Serial.print("Connecting to "); Serial.println(ssid); - WiFi.begin(ssid, password); + WiFi.begin(ssid, password, channel); WiFi.setAutoReconnect(true); // Enable auto reconnect - wl_status_t result = static_cast(WiFi.waitForConnectResult(wifi_connect_timeout)); - Serial.println(getConnectResultString(result)); - - if (result == WL_CONNECTED) { // WL_CONNECTED is assigned when connected to a WiFi network - wifi_connected = true; - wifi_state = "Connected"; - // Print local IP address and start web server - Serial.println(""); - Serial.print("Connected to WiFi network: "); - Serial.println(ssid); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - Serial.println("Signal Strength: " + String(WiFi.RSSI()) + " dBm"); - } else { - wifi_connected = false; - wifi_state = "Not connected"; - Serial.print("Not connected to WiFi network: "); - Serial.println(ssid); - Serial.println("Please check WiFi network name and password, and if WiFi network is available."); - } } void init_ElegantOTA() { diff --git a/Software/src/devboard/webserver/webserver.h b/Software/src/devboard/webserver/webserver.h index b2bca0e8..efb4b3f1 100644 --- a/Software/src/devboard/webserver/webserver.h +++ b/Software/src/devboard/webserver/webserver.h @@ -40,6 +40,7 @@ extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; extern const char* ssid; extern const char* password; +extern const uint8_t channel; extern const char* ssidAP; extern const char* passwordAP; extern const char* versionNumber; @@ -64,6 +65,15 @@ extern uint16_t OBC_Charge_Power; */ void init_webserver(); +/* + * @brief loop for monitoring wifi. + * + * @param[in] void + * + * @return void +*/ +void wifi_monitor(); + /** * @brief Initialization function that creates a WiFi Access Point. * @@ -81,7 +91,7 @@ void init_WiFi_AP(); * * @return void */ -void init_WiFi_STA(const char* ssid, const char* password); +void init_WiFi_STA(const char* ssid, const char* password, const uint8_t channel); /** * @brief Initialization function for ElegantOTA. From 1a32022949c9b2e4d17bc0fe99831d4b05f12c8d Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Fri, 9 Feb 2024 08:12:51 +1100 Subject: [PATCH 28/44] fix events endpoint --- Software/src/devboard/webserver/webserver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index a5fe7ebc..78122465 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -75,7 +75,7 @@ void init_webserver() { request->send_P(200, "text/html", index_html, cellmonitor_processor); }); - server.on("events", HTTP_GET, [](AsyncWebServerRequest* request) { + server.on("/events", HTTP_GET, [](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, events_processor); }); From 91c7a001cce8e3ad1360ce84de1187117e82f94e Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Fri, 9 Feb 2024 08:58:45 +1100 Subject: [PATCH 29/44] publish common info autoconfig --- Software/src/devboard/mqtt/mqtt.cpp | 105 ++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 7b7de371..76b61c11 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -26,22 +26,6 @@ static void publish_values(void) { publish_cell_voltages(); } -static void publish_common_info(void) { - snprintf(mqtt_msg, sizeof(mqtt_msg), - "{\n" - " \"SOC\": %.3f,\n" - " \"StateOfHealth\": %.3f,\n" - " \"temperature_min\": %.3f,\n" - " \"temperature_max\": %.3f,\n" - " \"cell_max_voltage\": %d,\n" - " \"cell_min_voltage\": %d\n" - "}\n", - ((float)SOC) / 100.0, ((float)StateOfHealth) / 100.0, ((float)((int16_t)temperature_min)) / 10.0, - ((float)((int16_t)temperature_max)) / 10.0, cell_max_voltage, cell_min_voltage); - bool result = client.publish("battery/info", mqtt_msg, true); - //Serial.println(mqtt_msg); // Uncomment to print the payload on serial -} - static void publish_cell_voltages(void) { static bool mqtt_first_transmission = true; @@ -76,12 +60,12 @@ static void publish_cell_voltages(void) { "\"object_id\": \"sensor_battery_voltage_cell%d\"," "\"origin\": {" "\"name\": \"BatteryEmulator\"," - "\"sw\": \"4.4.0-mqtt\"," + "\"sw\": \"5.1.0-mqtt\"," "\"url\": \"https://github.com/dalathegreat/Battery-Emulator\"" "}," "\"state_class\": \"measurement\"," "\"name\": \"Battery Cell Voltage %d\"," - "\"state_topic\": \"battery/spec_data\"," + "\"state_topic\": \"battery-emulator/spec_data\"," "\"unique_id\": \"battery-emulator_battery_voltage_cell%d\"," "\"unit_of_measurement\": \"V\"," "\"value_template\": \"{{ value_json.cell_voltages[%d] }}\"" @@ -111,12 +95,90 @@ static void publish_cell_voltages(void) { snprintf(mqtt_msg + msg_length, sizeof(mqtt_msg) - msg_length, "]\n}\n"); // Publish and print error if not OK - if (mqtt_publish_retain("battery/spec_data") == false) { + if (mqtt_publish_retain("battery-emulator/spec_data") == false) { Serial.println("Cell voltage MQTT msg could not be sent"); } } } +struct SensorConfig { + const char* topic; + const char* name; + const char* value_template; + const char* unit; + const char* device_class; +}; + +SensorConfig sensorConfigs[] = { + {"homeassistant/sensor/battery-emulator/SOC/config", "Battery Emulator SOC", "{{ value_json.SOC }}", "%", "battery"}, + {"homeassistant/sensor/battery-emulator/StateOfHealth/config", "Battery Emulator StateOfHealth", "{{ value_json.StateOfHealth }}", "%", "battery"}, + {"homeassistant/sensor/battery-emulator/temperature_min/config", "Battery Emulator Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"}, + {"homeassistant/sensor/battery-emulator/temperature_max/config", "Battery Emulator Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"}, + {"homeassistant/sensor/battery-emulator/stat_batt_power/config", "Battery Emulator Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"}, + {"homeassistant/sensor/battery-emulator/battery_current/config", "Battery Emulator Battery Current", "{{ value_json.battery_current }}", "A", "current"}, + {"homeassistant/sensor/battery-emulator/cell_max_voltage/config", "Battery Emulator Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"}, + {"homeassistant/sensor/battery-emulator/cell_min_voltage/config", "Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, + {"homeassistant/sensor/battery-emulator/battery_voltage/config", "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, +}; + +static void publish_common_info(void) { + static bool mqtt_first_transmission = true; + if (mqtt_first_transmission == true) { + mqtt_first_transmission = false; + for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) { + SensorConfig& config = sensorConfigs[i]; + snprintf(mqtt_msg, sizeof(mqtt_msg), + "{" + "\"name\": \"%s\"," + "\"state_topic\": \"battery-emulator/info\"," + "\"json_attributes_topic\": \"battery-emulator/info\"," + "\"unique_id\": \"battery-emulator_%d\"," + "\"device\": {" + "\"identifiers\": [" + "\"battery-emulator\"" + "]," + "\"manufacturer\": \"DalaTech\"," + "\"model\": \"BatteryEmulator\"," + "\"name\": \"BatteryEmulator\"," + "\"sw_version\": \"4.4.0-mqtt\"," + "\"url\": \"https://github.com/dalathegreat/Battery-Emulator\"" + "}," + "\"value_template\": \"%s\"," + "\"unit_of_measurement\": \"%s\"," + "\"device_class\": \"%s\"," + "\"state_class\": \"measurement\"" + "}", + config.name, i, config.value_template, config.unit, config.device_class); + mqtt_publish_retain(config.topic); + } + } else { + snprintf(mqtt_msg, sizeof(mqtt_msg), + "{\n" + " \"SOC\": %.3f,\n" + " \"StateOfHealth\": %.3f,\n" + " \"temperature_min\": %.3f,\n" + " \"temperature_max\": %.3f,\n" + " \"stat_batt_power\": %.3f,\n" + " \"battery_current\": %.3f,\n" + " \"cell_max_voltage\": %d,\n" + " \"cell_min_voltage\": %d\n" + " \"battery_voltage\": %d\n" + "}\n", + ((float)SOC) / 100.0, + ((float)StateOfHealth) / 100.0, + ((float)((int16_t)temperature_min)) / 10.0, + ((float)((int16_t)temperature_max)) / 10.0, + ((float)((int16_t)stat_batt_power)) / 10.0, + ((float)((int16_t)battery_current)) / 10.0, + cell_max_voltage, + cell_min_voltage, + battery_voltage / 10.0); + bool result = client.publish("battery-emulator/info", mqtt_msg, true); + } + + //Serial.println(mqtt_msg); // Uncomment to print the payload on serial +} + /* This is called whenever a subscribed topic changes (hopefully) */ static void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Message arrived ["); @@ -132,9 +194,8 @@ static void callback(char* topic, byte* payload, unsigned int length) { static void reconnect() { // attempt one reconnection Serial.print("Attempting MQTT connection... "); - // Create a random client ID - String clientId = "LilyGoClient-"; - clientId += String(random(0xffff), HEX); + const char* hostname = WiFi.getHostname(); + String clientId = "LilyGoClient-" + String(hostname); // Attempt to connect if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) { Serial.println("connected"); From 9f68296f5fd45e9342d5119b9e882c5e5ffcbc96 Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Fri, 9 Feb 2024 10:55:49 +1100 Subject: [PATCH 30/44] fixing autoconfig --- Software/src/devboard/mqtt/mqtt.cpp | 52 ++++++++++++++++------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/Software/src/devboard/mqtt/mqtt.cpp b/Software/src/devboard/mqtt/mqtt.cpp index 76b61c11..92661c94 100644 --- a/Software/src/devboard/mqtt/mqtt.cpp +++ b/Software/src/devboard/mqtt/mqtt.cpp @@ -102,6 +102,7 @@ static void publish_cell_voltages(void) { } struct SensorConfig { + const char* object_id; const char* topic; const char* name; const char* value_template; @@ -110,19 +111,20 @@ struct SensorConfig { }; SensorConfig sensorConfigs[] = { - {"homeassistant/sensor/battery-emulator/SOC/config", "Battery Emulator SOC", "{{ value_json.SOC }}", "%", "battery"}, - {"homeassistant/sensor/battery-emulator/StateOfHealth/config", "Battery Emulator StateOfHealth", "{{ value_json.StateOfHealth }}", "%", "battery"}, - {"homeassistant/sensor/battery-emulator/temperature_min/config", "Battery Emulator Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"}, - {"homeassistant/sensor/battery-emulator/temperature_max/config", "Battery Emulator Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"}, - {"homeassistant/sensor/battery-emulator/stat_batt_power/config", "Battery Emulator Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"}, - {"homeassistant/sensor/battery-emulator/battery_current/config", "Battery Emulator Battery Current", "{{ value_json.battery_current }}", "A", "current"}, - {"homeassistant/sensor/battery-emulator/cell_max_voltage/config", "Battery Emulator Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"}, - {"homeassistant/sensor/battery-emulator/cell_min_voltage/config", "Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, - {"homeassistant/sensor/battery-emulator/battery_voltage/config", "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, + {"SOC", "homeassistant/sensor/battery-emulator/SOC/config", "Battery Emulator SOC", "{{ value_json.SOC }}", "%", "battery"}, + {"state_of_health", "homeassistant/sensor/battery-emulator/state_of_health/config", "Battery Emulator State Of Health", "{{ value_json.state_of_health }}", "%", "battery"}, + {"temperature_min", "homeassistant/sensor/battery-emulator/temperature_min/config", "Battery Emulator Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"}, + {"temperature_max", "homeassistant/sensor/battery-emulator/temperature_max/config", "Battery Emulator Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"}, + {"stat_batt_power", "homeassistant/sensor/battery-emulator/stat_batt_power/config", "Battery Emulator Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"}, + {"battery_current", "homeassistant/sensor/battery-emulator/battery_current/config", "Battery Emulator Battery Current", "{{ value_json.battery_current }}", "A", "current"}, + {"cell_max_voltage", "homeassistant/sensor/battery-emulator/cell_max_voltage/config", "Battery Emulator Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"}, + {"cell_min_voltage", "homeassistant/sensor/battery-emulator/cell_min_voltage/config", "Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"}, + {"battery_voltage", "homeassistant/sensor/battery-emulator/battery_voltage/config", "Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"}, }; static void publish_common_info(void) { static bool mqtt_first_transmission = true; + static char* state_topic = "battery-emulator/info"; if (mqtt_first_transmission == true) { mqtt_first_transmission = false; for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) { @@ -130,32 +132,36 @@ static void publish_common_info(void) { snprintf(mqtt_msg, sizeof(mqtt_msg), "{" "\"name\": \"%s\"," - "\"state_topic\": \"battery-emulator/info\"," - "\"json_attributes_topic\": \"battery-emulator/info\"," - "\"unique_id\": \"battery-emulator_%d\"," + "\"state_topic\": \"%s\"," + "\"unique_id\": \"battery-emulator_%s\"," + "\"object_id\": \"%s\"," "\"device\": {" - "\"identifiers\": [" - "\"battery-emulator\"" - "]," - "\"manufacturer\": \"DalaTech\"," - "\"model\": \"BatteryEmulator\"," - "\"name\": \"BatteryEmulator\"," - "\"sw_version\": \"4.4.0-mqtt\"," - "\"url\": \"https://github.com/dalathegreat/Battery-Emulator\"" + "\"identifiers\": [" + "\"battery-emulator\"" + "]," + "\"manufacturer\": \"DalaTech\"," + "\"model\": \"BatteryEmulator\"," + "\"name\": \"BatteryEmulator\"," + "}," + "\"origin\": {" + "\"name\": \"BatteryEmulator\"," + "\"sw\": \"5.1.0-mqtt\"," + "\"url\": \"https://github.com/dalathegreat/Battery-Emulator\"" "}," "\"value_template\": \"%s\"," "\"unit_of_measurement\": \"%s\"," "\"device_class\": \"%s\"," + "\"enabled_by_default\": true," "\"state_class\": \"measurement\"" "}", - config.name, i, config.value_template, config.unit, config.device_class); + config.name, state_topic, config.object_id, config.object_id, config.value_template, config.unit, config.device_class); mqtt_publish_retain(config.topic); } } else { snprintf(mqtt_msg, sizeof(mqtt_msg), "{\n" " \"SOC\": %.3f,\n" - " \"StateOfHealth\": %.3f,\n" + " \"state_of_health\": %.3f,\n" " \"temperature_min\": %.3f,\n" " \"temperature_max\": %.3f,\n" " \"stat_batt_power\": %.3f,\n" @@ -173,7 +179,7 @@ static void publish_common_info(void) { cell_max_voltage, cell_min_voltage, battery_voltage / 10.0); - bool result = client.publish("battery-emulator/info", mqtt_msg, true); + bool result = client.publish(state_topic, mqtt_msg, true); } //Serial.println(mqtt_msg); // Uncomment to print the payload on serial From a928ed1cea1e9fde984519896b6f6032e50d745f Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Fri, 9 Feb 2024 11:58:42 +1100 Subject: [PATCH 31/44] trying to be more efficient... --- Software/src/devboard/utils/events.cpp | 15 +++++ Software/src/devboard/utils/events.h | 2 + Software/src/devboard/webserver/webserver.cpp | 66 ++++++++++--------- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 35a8d256..25b25101 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -54,6 +54,21 @@ 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 "RED"; + case YELLOW: + return "YELLOW"; + case GREEN: + return "GREEN"; + case BLUE: + return "BLUE"; + default: + return "UNKNOWN"; + } +} + const char* get_event_message(EVENTS_ENUM_TYPE event) { switch (event) { case EVENT_CAN_FAILURE: diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index c75fa65a..7d395bf5 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -42,6 +42,8 @@ const char* get_event_enum_string(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); + void init_events(void); void set_event(EVENTS_ENUM_TYPE event, uint8_t data); void update_event_timestamps(void); diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 78122465..5888e0fb 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -385,6 +385,7 @@ String processor(const String& var) { content += "

IP: " + WiFi.localIP().toString() + "

"; // Get and display the signal strength (RSSI) content += "

Signal Strength: " + String(WiFi.RSSI()) + " dBm

"; + content += "

Channel: " + String(WiFi.channel()) + "

"; } // Close the block content += ""; @@ -941,42 +942,47 @@ String cellmonitor_processor(const String& var) { return String(); } +const char EVENTS_HTML_START[] PROGMEM = R"=====( + +
+

Event log:

+ + +)====="; +const char EVENTS_HTML_END[] PROGMEM = R"=====( +
Event TypeLED ColorLast Event (seconds ago)CountDataMessage
+
+ + +)====="; + String events_processor(const String& var) { if (var == "PLACEHOLDER") { String content = ""; + content.reserve(5000); // Page format - content += ""; - - // Start a new block with a specific background color - content += "
"; - - //iterate through entries and display count, time and event - content += "

Event log:

"; - content += ""; - content += ""; + content.concat(FPSTR(EVENTS_HTML_START)); for(int i = 0; i < EVENT_NOF_EVENTS; i++) { - content += ""; - content += ""; - content += ""; - content += ""; - content += ""; - content += ""; - content += ""; - content += ""; + if (entries[i].occurences > 0) { + content.concat(""); + content.concat(""); + content.concat(""); + content.concat(""); + content.concat(""); + content.concat(""); + content.concat(""); + content.concat(""); + } } - content += "
Event TypeLED ColorLast Event (seconds ago)CountDataMessage
" + String(get_event_enum_string(static_cast(i))) + "" + String(entries[i].led_color) + "" + String((millis() / 1000) - entries[i].timestamp) + "" + String(entries[i].occurences) + "" + String(entries[i].data) + "" + String(get_event_message(static_cast(i))) + "
" + String(get_event_enum_string(static_cast(i))) + "" + String(get_led_color_display_text(entries[i].led_color)) + "" + String((millis() / 1000) - entries[i].timestamp) + "" + String(entries[i].occurences) + "" + String(entries[i].data) + "" + String(get_event_message(static_cast(i))) + "
"; - - // Close the block - content += "
"; - - content += ""; - content += ""; + content.concat(FPSTR(EVENTS_HTML_END)); return content; } return String(); From ce0e52142a2be5b1024bde25e3308626dfb430f7 Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Fri, 9 Feb 2024 14:05:43 +1100 Subject: [PATCH 32/44] disable setting yellow led for kwh implausible model 3 --- Software/src/battery/TESLA-MODEL-3-BATTERY.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp index c8123929..9c2e544b 100644 --- a/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp +++ b/Software/src/battery/TESLA-MODEL-3-BATTERY.cpp @@ -266,10 +266,13 @@ void update_values_tesla_model_3_battery() { //This function maps all the value } //Check if BMS is in need of recalibration - if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) { - Serial.println("Warning: kWh remaining reported by battery not plausible. Battery needs cycling."); + if (nominal_full_pack_energy > 1 &&nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) { + Serial.println("Warning: kWh remaining " + String(nominal_full_pack_energy) + " reported by battery not plausible. Battery needs cycling."); set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy); LEDcolor = YELLOW; + } else if (nominal_full_pack_energy <= 1) { + Serial.println("Info: kWh remaining battery is not reporting kWh remaining."); + set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy); } if (LFP_Chemistry) { //LFP limits used for voltage safeties From 0993b52553ee02cebf0b66c65a1e218f0e9d104b Mon Sep 17 00:00:00 2001 From: Brett Christensen Date: Fri, 9 Feb 2024 14:06:36 +1100 Subject: [PATCH 33/44] fix up event log page and add hostname to output of webserver --- Software/src/devboard/utils/events.cpp | 11 ++++-- Software/src/devboard/utils/events.h | 2 +- Software/src/devboard/webserver/webserver.cpp | 38 ++++++++++--------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/Software/src/devboard/utils/events.cpp b/Software/src/devboard/utils/events.cpp index 25b25101..9fc064e2 100644 --- a/Software/src/devboard/utils/events.cpp +++ b/Software/src/devboard/utils/events.cpp @@ -7,6 +7,7 @@ static unsigned long previous_millis = 0; static uint32_t time_seconds = 0; static uint8_t total_led_color = GREEN; static char event_message[256]; +EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; /* Local function prototypes */ static void set_event_message(EVENTS_ENUM_TYPE event); @@ -34,10 +35,10 @@ void set_event(EVENTS_ENUM_TYPE event, uint8_t data) { } entries[event].timestamp = time_seconds; entries[event].data = data; - entries[event].occurences++; + ++entries[event].occurences; set_event_message(event); #ifdef DEBUG_VIA_USB - Serial.println(event_message); + Serial.println("Set event: " + String(get_event_enum_string(event)) + ". Has occured " + String(entries[event].occurences) + " times"); #endif } @@ -111,7 +112,11 @@ const char* get_event_message(EVENTS_ENUM_TYPE event) { } const char* get_event_enum_string(EVENTS_ENUM_TYPE event) { - return EVENTS_ENUM_TYPE_STRING[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; } static void set_event_message(EVENTS_ENUM_TYPE event) { diff --git a/Software/src/devboard/utils/events.h b/Software/src/devboard/utils/events.h index 7d395bf5..ba8a0ba9 100644 --- a/Software/src/devboard/utils/events.h +++ b/Software/src/devboard/utils/events.h @@ -53,6 +53,6 @@ typedef struct { uint8_t occurences; // Number of occurrences since startup uint8_t led_color; // Weirdly indented comment } EVENTS_STRUCT_TYPE; -static EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; +extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; #endif // __MYTIMER_H__ diff --git a/Software/src/devboard/webserver/webserver.cpp b/Software/src/devboard/webserver/webserver.cpp index 5888e0fb..31b45234 100644 --- a/Software/src/devboard/webserver/webserver.cpp +++ b/Software/src/devboard/webserver/webserver.cpp @@ -318,9 +318,10 @@ void wifi_monitor() { wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL; // Print local IP address and start web server Serial.print("Connected to WiFi network: " + String(ssid)); - Serial.print("IP address: " + WiFi.localIP().toString()); - Serial.print("Signal Strength: " + String(WiFi.RSSI()) + " dBm "); - Serial.println("Channel: " + String(WiFi.channel())); + Serial.print(" IP address: " + WiFi.localIP().toString()); + Serial.print(" Signal Strength: " + String(WiFi.RSSI()) + " dBm"); + Serial.println(" Channel: " + String(WiFi.channel())); + Serial.println(" Hostname: " + String(WiFi.getHostname())); } } } @@ -945,17 +946,19 @@ String cellmonitor_processor(const String& var) { const char EVENTS_HTML_START[] PROGMEM = R"=====(

Event log:

- - +
+
+
Event Type
LED Color
Last Event (seconds ago)
Count
Data
Message
+
)====="; const char EVENTS_HTML_END[] PROGMEM = R"=====( -
Event TypeLED ColorLast Event (seconds ago)CountDataMessage
-
+