From fd4174c091d5bcb9880c4b0741c49fd3af96732b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Sun, 5 Apr 2026 11:35:22 +0200 Subject: [PATCH 01/23] Refactor protections around a static rule engine --- Inc/C++Utilities/CppImports.hpp | 7 + Inc/C++Utilities/CppUtils.hpp | 8 + Inc/ST-LIB_HIGH/Protections/Boundary.hpp | 774 ------------------ Inc/ST-LIB_HIGH/Protections/Notification.hpp | 103 --- Inc/ST-LIB_HIGH/Protections/Protection.hpp | 594 ++++++++++++-- .../Protections/ProtectionConcepts.hpp | 46 ++ .../Protections/ProtectionEngine.hpp | 97 +++ .../Protections/ProtectionErrors.hpp | 23 + .../Protections/ProtectionManager.hpp | 102 --- .../Protections/ProtectionTypes.hpp | 118 +++ Inc/ST-LIB_HIGH/Protections/Rules.hpp | 284 +++++++ Inc/ST-LIB_HIGH/Protections/SampleSource.hpp | 18 + Inc/ST-LIB_HIGH/ST-LIB_HIGH.hpp | 4 +- Src/ST-LIB_HIGH/Protections/Boundary.cpp | 16 - .../Protections/ProtectionEngine.cpp | 105 +++ .../Protections/ProtectionManager.cpp | 186 ----- Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp | 2 +- 17 files changed, 1222 insertions(+), 1265 deletions(-) delete mode 100644 Inc/ST-LIB_HIGH/Protections/Boundary.hpp delete mode 100644 Inc/ST-LIB_HIGH/Protections/Notification.hpp create mode 100644 Inc/ST-LIB_HIGH/Protections/ProtectionConcepts.hpp create mode 100644 Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp create mode 100644 Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp delete mode 100644 Inc/ST-LIB_HIGH/Protections/ProtectionManager.hpp create mode 100644 Inc/ST-LIB_HIGH/Protections/ProtectionTypes.hpp create mode 100644 Inc/ST-LIB_HIGH/Protections/Rules.hpp create mode 100644 Inc/ST-LIB_HIGH/Protections/SampleSource.hpp delete mode 100644 Src/ST-LIB_HIGH/Protections/Boundary.cpp create mode 100644 Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp delete mode 100644 Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp diff --git a/Inc/C++Utilities/CppImports.hpp b/Inc/C++Utilities/CppImports.hpp index d28013f2f..e56472bd0 100644 --- a/Inc/C++Utilities/CppImports.hpp +++ b/Inc/C++Utilities/CppImports.hpp @@ -42,3 +42,10 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include diff --git a/Inc/C++Utilities/CppUtils.hpp b/Inc/C++Utilities/CppUtils.hpp index f94664fee..e761d222f 100644 --- a/Inc/C++Utilities/CppUtils.hpp +++ b/Inc/C++Utilities/CppUtils.hpp @@ -8,6 +8,10 @@ namespace chrono = std::chrono; namespace placeholders = std::placeholders; using std::array; +using std::byte; +using std::construct_at; +using std::destroy_at; +using std::expected; using std::function; using std::hash; using std::integral_constant; @@ -17,6 +21,7 @@ using std::make_unique; using std::map; using std::move; using std::nullopt; +using std::optional; using std::pair; using std::queue; using std::reference_wrapper; @@ -29,6 +34,9 @@ using std::stack; using std::string; using std::stringstream; using std::to_string; +using std::unexpected; using std::unique_ptr; using std::unordered_map; +using std::variant; using std::vector; +using std::visit; diff --git a/Inc/ST-LIB_HIGH/Protections/Boundary.hpp b/Inc/ST-LIB_HIGH/Protections/Boundary.hpp deleted file mode 100644 index 1f6539dc9..000000000 --- a/Inc/ST-LIB_HIGH/Protections/Boundary.hpp +++ /dev/null @@ -1,774 +0,0 @@ -#pragma once -#define PROTECTIONTYPE_LENGTH 8 -#include "C++Utilities/CppUtils.hpp" -#include "Control/Blocks/MeanCalculator.hpp" -#include "ErrorHandler/ErrorHandler.hpp" -#include "HALAL/Models/Packets/Order.hpp" -#include "HALAL/Services/InfoWarning/InfoWarning.hpp" -#include "HALAL/Services/Time/RTC.hpp" - -using type_id_t = void (*)(); -template void type_id() {} - -namespace Protections { -enum FaultType : uint8_t { FAULT = 0, WARNING, OK }; -} - -enum ProtectionType : uint8_t { - BELOW = 0, - ABOVE, - OUT_OF_RANGE, - EQUALS, - NOT_EQUALS, - ERROR_HANDLER, - TIME_ACCUMULATION, - INFO_WARNING -}; - -struct BoundaryInterface { -public: - static constexpr uint8_t ERROR_HANDLER_BOUNDARY_TYPE_ID = ERROR_HANDLER; - static constexpr uint8_t INFO_WARNING_BOUNDARY_TYPE_ID = INFO_WARNING - 2; - - virtual Protections::FaultType check_bounds() = 0; - HeapOrder* fault_message{nullptr}; - HeapOrder* warn_message{nullptr}; - HeapOrder* ok_message{nullptr}; - void update_name(char* n) { - if (n == nullptr) { - name.clear(); - string_len = 0; - return; - } - - name = n; - if (name.size() > NAME_MAX_LEN) { - name.resize(NAME_MAX_LEN); - } - string_len = name.size(); - } - virtual void update_error_handler_message([[maybe_unused]] const char* err_message) {} - virtual void update_warning_message([[maybe_unused]] const char* warn_message) {} - static const char* get_error_handler_string() { return ErrorHandlerModel::description.c_str(); } - static const char* get_warning_string() { return InfoWarning::description.c_str(); } - uint8_t boundary_type_id{}; - // used to send messages only on raising/failing edges - bool warning_already_triggered{false}; - bool warning_is_up{false}; - bool back_to_normal{false}; - -protected: - static const map format_look_up; - static int get_error_handler_string_size() { return ErrorHandlerModel::description.size(); } - static int get_warning_string_size() { return InfoWarning::description.size(); } - - // this will store the name of the variable - string name; - // max variable name - static constexpr uint8_t NAME_MAX_LEN = 40; - uint8_t format_id{255}; - uint8_t string_len{0}; -}; - -template struct Boundary; - -template struct Boundary : public BoundaryInterface { - static constexpr ProtectionType Protector = BELOW; - bool has_warning_level{false}; - Type* src = nullptr; - Type boundary; - Type warning_threshold{std::numeric_limits::max()}; - // to get a snapshot of the value when the protection is triggered - Type frozen_value{}; - constexpr Boundary(const Type warn, const Type bound) - : has_warning_level{true}, boundary(bound), warning_threshold(warn) { - // i havent been able to find a way to do a static_assertion. - if (warn < bound) { - ErrorHandler("Warning threshold is below boundary"); - } - }; - Boundary(Type boundary) : boundary(boundary) {} - Boundary(Type* src, Boundary boundary) - : has_warning_level(boundary.has_warning_level), src(src), boundary(boundary.boundary) { - // we have to do this because we cannot take address of rvalue - // (ProtectionType::BELOW) - boundary_type_id = Protector; - format_id = BoundaryInterface::format_look_up.at(type_id); - // we have to preallocate space, otherwise the might get moved around, - // invalidating the pointer, better safe than sorry - name.reserve(NAME_MAX_LEN); - if (this->has_warning_level) { - warning_threshold = boundary.warning_threshold; - warn_message = new HeapOrder( - uint16_t{2000}, - &format_id, - &boundary_type_id, - &name, - &this->warning_threshold, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - ok_message = new HeapOrder( - uint16_t{3000}, - &format_id, - &boundary_type_id, - &name, - &this->warning_threshold, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } else { - ok_message = new HeapOrder( - uint16_t{3000}, - &format_id, - &boundary_type_id, - &name, - &this->boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - - fault_message = new HeapOrder( - uint16_t{1000}, - &format_id, - &boundary_type_id, - &name, - &this->boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - - Boundary(Type* src, Type boundary) : src(src), boundary(boundary) {} - Protections::FaultType check_bounds() override { - frozen_value = *src; - if (*src < boundary) { - return Protections::FAULT; - } - if (has_warning_level && *src < warning_threshold) { - return Protections::WARNING; - } - return Protections::OK; - } -}; - -template struct Boundary : public BoundaryInterface { - static constexpr ProtectionType Protector = ABOVE; - bool has_warning_level{false}; - Type* src = nullptr; - Type boundary{}; - Type warning_threshold{}; - Type frozen_value{}; - - Boundary(Type warning_threshold, Type boundary) - : has_warning_level{true}, boundary(boundary), warning_threshold(warning_threshold) { - if (warning_threshold > boundary) { - ErrorHandler("Warning threshold is above boundary"); - } - }; - Boundary(Type boundary) : boundary(boundary){}; - Boundary(Type* src, Boundary boundary) - : has_warning_level(boundary.has_warning_level), src(src), boundary(boundary.boundary) { - // we have to do this because we cannot take address of rvalue - // (ProtectionType::BELOW) - boundary_type_id = Protector; - format_id = BoundaryInterface::format_look_up.at(type_id); - // we have to preallocate space, otherwise the might get moved around, - // invalidating the pointer, better safe than sorry - name.reserve(NAME_MAX_LEN); - if (this->has_warning_level) { - warning_threshold = boundary.warning_threshold; - warn_message = new HeapOrder( - uint16_t{2111}, - &format_id, - &boundary_type_id, - &name, - &this->warning_threshold, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - ok_message = new HeapOrder( - uint16_t{3111}, - &format_id, - &boundary_type_id, - &name, - &this->warning_threshold, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - - } else { - ok_message = new HeapOrder( - uint16_t{3111}, - &format_id, - &boundary_type_id, - &name, - &this->boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - - fault_message = new HeapOrder( - uint16_t{1111}, - &format_id, - &boundary_type_id, - &name, - &this->boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - Boundary(Type* src, Type boundary) : src(src), boundary(boundary) {} - Protections::FaultType check_bounds() override { - frozen_value = *src; - if (*src > boundary) - return Protections::FAULT; - if (has_warning_level && *src > warning_threshold) { - return Protections::WARNING; - } - return Protections::OK; - } -}; - -template struct Boundary : public BoundaryInterface { - static constexpr ProtectionType Protector = EQUALS; - Type* src = nullptr; - Type boundary; - Type frozen_value{}; - - Boundary(Type boundary) : boundary(boundary){}; - Boundary(Type* src, Boundary boundary) - : src(src), boundary(boundary.boundary) { - boundary_type_id = Protector; - format_id = BoundaryInterface::format_look_up.at(type_id); - name.reserve(NAME_MAX_LEN); - fault_message = new HeapOrder( - uint16_t{1333}, - &format_id, - &boundary_type_id, - &name, - &this->boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - ok_message = new HeapOrder( - uint16_t{2333}, - &format_id, - &boundary_type_id, - &name, - &this->boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - Boundary(Type* src, Type boundary) : src(src), boundary(boundary) {} - Protections::FaultType check_bounds() override { - if (*src == boundary) - return Protections::FAULT; - return Protections::OK; - } -}; - -template struct Boundary : public BoundaryInterface { - static constexpr ProtectionType Protector = NOT_EQUALS; - Type* src = nullptr; - Type boundary; - Type frozen_value{}; - - Boundary(Type boundary) : boundary(boundary){}; - Boundary(Type* src, Boundary boundary) - : src(src), boundary(boundary.boundary) { - boundary_type_id = Protector; - format_id = BoundaryInterface::format_look_up.at(type_id); - name.reserve(NAME_MAX_LEN); - fault_message = new HeapOrder( - uint16_t{1444}, - &format_id, - &boundary_type_id, - &name, - &this->boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - ok_message = new HeapOrder( - uint16_t{2444}, - &format_id, - &boundary_type_id, - &name, - &this->boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - Boundary(Type* src, Type boundary) : src(src), boundary(boundary) {} - Protections::FaultType check_bounds() override { - frozen_value = *src; - if (*src != boundary) - return Protections::FAULT; - return Protections::OK; - } -}; - -template struct Boundary : public BoundaryInterface { - static constexpr ProtectionType Protector = OUT_OF_RANGE; - Type* src = nullptr; - Type lower_warning; - Type upper_warning; - Type lower_boundary; - Type upper_boundary; - Type frozen_value{}; - - bool has_warning_level{false}; - Boundary(Type lower_warning, Type upper_warning, Type lower_boundary, Type upper_boundary) - : lower_warning(lower_warning), upper_warning(upper_warning), - lower_boundary(lower_boundary), upper_boundary(upper_boundary), has_warning_level{true} { - if (lower_warning < lower_boundary || upper_warning > upper_boundary) { - ErrorHandler("Warning thresholds are outside of boundaries"); - } - }; - Boundary(Type lower_boundary, Type upper_boundary) - : lower_warning(std::numeric_limits::min()), - upper_warning(std::numeric_limits::max()), lower_boundary(lower_boundary), - upper_boundary(upper_boundary){}; - Boundary(Type* src, Boundary boundary) - : src(src), lower_boundary(boundary.lower_boundary), - upper_boundary(boundary.upper_boundary) { - boundary_type_id = Protector; - format_id = BoundaryInterface::format_look_up.at(type_id); - name.reserve(NAME_MAX_LEN); - if (boundary.has_warning_level) { - lower_warning = boundary.lower_warning; - upper_warning = boundary.upper_warning; - warn_message = new HeapOrder( - uint16_t{2222}, - &format_id, - &boundary_type_id, - &name, - &boundary.lower_boundary, - &boundary.upper_boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - ok_message = new HeapOrder( - uint16_t{3222}, - &format_id, - &boundary_type_id, - &name, - &boundary.lower_warning, - &boundary.upper_warning, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - - } else { - ok_message = new HeapOrder( - uint16_t{3222}, - &format_id, - &boundary_type_id, - &name, - &this->lower_boundary, - &this->upper_boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - - fault_message = new HeapOrder( - uint16_t{1222}, - &format_id, - &boundary_type_id, - &name, - &this->lower_boundary, - &this->upper_boundary, - &this->frozen_value, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - Boundary(Type* src, Type lower_boundary, Type upper_boundary) - : src(src), lower_boundary(lower_boundary), upper_boundary(upper_boundary) {} - Protections::FaultType check_bounds() override { - frozen_value = *src; - if (*src < lower_boundary || *src > upper_boundary) - return Protections::FAULT; - if (has_warning_level && ((*src < lower_boundary) || (*src > upper_boundary))) { - return Protections::WARNING; - } - return Protections::OK; - } -}; - -template <> struct Boundary : public BoundaryInterface { - static constexpr ProtectionType Protector = ERROR_HANDLER; - Boundary(void*) { - boundary_type_id = ERROR_HANDLER_BOUNDARY_TYPE_ID; - error_handler_string.reserve(ERROR_HANDLER_MSG_MAX_LEN); - fault_message = new HeapOrder( - uint16_t{1555}, - &padding, - &boundary_type_id, - &name, - &error_handler_string, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - uint8_t padding{}; - Boundary(void*, Boundary) { - boundary_type_id = ERROR_HANDLER_BOUNDARY_TYPE_ID; - error_handler_string.reserve(ERROR_HANDLER_MSG_MAX_LEN); - fault_message = new HeapOrder( - uint16_t{1555}, - &padding, - &boundary_type_id, - &name, - &error_handler_string, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - Boundary() = default; - Protections::FaultType check_bounds() override { - return not ErrorHandlerModel::error_triggered ? Protections::OK : Protections::FAULT; - } - void update_error_handler_message(const char* err_message) override { - error_handler_string = err_message; - if (strlen(err_message) > ERROR_HANDLER_MSG_MAX_LEN) { - ErrorHandler( - "Error Handler message is too long, max length is %d", - ERROR_HANDLER_MSG_MAX_LEN - ); - return; - } - error_handler_string_len = error_handler_string.size(); - } - -private: - string error_handler_string{}; - uint16_t error_handler_string_len{}; - static constexpr uint16_t ERROR_HANDLER_MSG_MAX_LEN = 255; -}; - -template <> struct Boundary : public BoundaryInterface { - static constexpr ProtectionType Protector = INFO_WARNING; - Boundary(void*) { - boundary_type_id = INFO_WARNING_BOUNDARY_TYPE_ID; - warning_string.reserve(WARNING_HANDLER_MSG_MAX_LEN); - warn_message = new HeapOrder( - uint16_t{2555}, - &padding, - &boundary_type_id, - &name, - &warning_string, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - uint8_t padding{}; - Boundary(void*, Boundary) { - // SW are crybabies - boundary_type_id = INFO_WARNING_BOUNDARY_TYPE_ID; - warning_string.reserve(WARNING_HANDLER_MSG_MAX_LEN); - warn_message = new HeapOrder( - uint16_t{2555}, - &padding, - &boundary_type_id, - &name, - &warning_string, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - Boundary() = default; - Protections::FaultType check_bounds() override { - return InfoWarning::warning_triggered ? Protections::WARNING : Protections::OK; - } - void update_warning_message(const char* warn_message) override { - warning_string = warn_message; - if (strlen(warn_message) > WARNING_HANDLER_MSG_MAX_LEN) { - ErrorHandler( - "Error Handler message is too long, max length is %d", - WARNING_HANDLER_MSG_MAX_LEN - ); - return; - } - warning_string_len = warning_string.size(); - } - -private: - string warning_string{}; - uint16_t warning_string_len{}; - static constexpr uint16_t WARNING_HANDLER_MSG_MAX_LEN = 255; -}; - -template - requires(std::is_floating_point_v) -struct Boundary : public BoundaryInterface { - static constexpr ProtectionType Protector = TIME_ACCUMULATION; - Boundary( - Type bound, - float time_limit, - float frequency, - Boundary*& external_pointer - ) - : real_still_good(new Protections::FaultType{Protections::OK}), bound(bound), - time_limit(time_limit), frequency(frequency), moving_order(frequency * time_limit / 100), - external_pointer(&external_pointer) { - external_pointer = this; - }; - Boundary( - Type warning_threshold, - Type bound, - float time_limit, - float frequency, - Boundary*& external_pointer - ) - : real_still_good(new Protections::FaultType{Protections::OK}), bound(bound), - time_limit(time_limit), frequency(frequency), moving_order(frequency * time_limit / 100), - external_pointer(&external_pointer) { - external_pointer = this; - has_warning_level = true; - this->warning_threshold = warning_threshold; - }; - Boundary(Type* src, Boundary boundary) - : real_still_good(boundary.real_still_good), src(src), bound(boundary.bound), - time_limit(boundary.time_limit), frequency(boundary.frequency), - moving_order(frequency * time_limit / 100), external_pointer(boundary.external_pointer) { - *external_pointer = this; - boundary_type_id = Protector; - format_id = BoundaryInterface::format_look_up.at(type_id); - name.reserve(NAME_MAX_LEN); - if (boundary.has_warning_level) { - warning_threshold = boundary.warning_threshold; - warn_message = new HeapOrder( - uint16_t{2666}, - &format_id, - &boundary_type_id, - &name, - &this->warning_threshold, - &this->bound, - &this->frozen_value, - &this->time_limit, - &this->frequency, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - ok_message = new HeapOrder( - uint16_t{3666}, - &format_id, - &boundary_type_id, - &name, - &this->warning_threshold, - &this->bound, - &this->frozen_value, - &this->time_limit, - &this->frequency, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - fault_message = new HeapOrder( - uint16_t{1666}, - &format_id, - &boundary_type_id, - &name, - &this->bound, - &this->frozen_value, - &this->time_limit, - &this->frequency, - &Global_RTC::global_RTC.counter, - &Global_RTC::global_RTC.second, - &Global_RTC::global_RTC.minute, - &Global_RTC::global_RTC.hour, - &Global_RTC::global_RTC.day, - &Global_RTC::global_RTC.month, - &Global_RTC::global_RTC.year - ); - } - Boundary(Type* src, Type bound, float time_limit, float frequency) - : real_still_good(new Protections::FaultType{Protections::OK}), src(src), bound(bound), - time_limit(time_limit), frequency(frequency), moving_order(frequency * time_limit / 100), - external_pointer(nullptr) {} - bool has_warning_level{false}; - Type warning_threshold; - uint8_t format_id{}; - Type* src = nullptr; - Type bound; - float time_limit; - float frequency; - Protections::FaultType* real_still_good = nullptr; - Protections::FaultType still_good = Protections::OK; - Boundary** external_pointer; - - MeanCalculator<100> mean_calculator; - vector mean_moving_average; - uint16_t moving_order = 0; - uint16_t moving_last = -1; - uint16_t moving_first = 0; - uint16_t moving_counter = 0; - Type accumulator{}; - - Protections::FaultType check_accumulation(Type value) { - if (still_good == Protections::FAULT) - return Protections::FAULT; - mean_calculator.input(abs(value)); - mean_calculator.execute(); - if (mean_calculator.output_value == 0) { - return Protections::OK; - } - mean_calculator.reset(); - if (moving_counter < moving_order) { - moving_last++; - mean_moving_average[moving_last] = mean_calculator.output_value; - accumulator += mean_calculator.output_value / moving_order; - moving_counter++; - return Protections::OK; - } - accumulator -= mean_moving_average[moving_first] / moving_order; - moving_first = (moving_first + 1) % moving_order; - moving_last = (moving_last + 1) % moving_counter; - mean_moving_average[moving_last] = mean_calculator.output_value; - accumulator += mean_moving_average[moving_last] / moving_order; - // we check by decreasing order. - if (accumulator > bound) { - *real_still_good = Protections::FAULT; - still_good = Protections::FAULT; - return Protections::FAULT; - } else if (has_warning_level && accumulator > warning_threshold) { - return Protections::WARNING; - } - return Protections::OK; - } - - Protections::FaultType check_bounds() override { - still_good = *real_still_good; - return still_good; - } -}; diff --git a/Inc/ST-LIB_HIGH/Protections/Notification.hpp b/Inc/ST-LIB_HIGH/Protections/Notification.hpp deleted file mode 100644 index 996e5e6fe..000000000 --- a/Inc/ST-LIB_HIGH/Protections/Notification.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -#include "C++Utilities/CppUtils.hpp" -#include "ErrorHandler/ErrorHandler.hpp" -#include "HALAL/Models/Packets/Order.hpp" -#include "Protection.hpp" - -class Notification : public Order { -private: - typedef uint16_t message_size_t; - uint16_t id; - void (*callback)() = nullptr; - string tx_message; - message_size_t tx_message_size; - string rx_message; - uint8_t* buffer = nullptr; - OrderProtocol* received_socket = nullptr; - -public: - Notification(uint16_t packet_id, void (*callback)(), string message) - : id(packet_id), callback(callback), tx_message(message), tx_message_size(message.size()) { - Order::orders[id] = this; - Packet::packets[id] = this; - } - - Notification(uint16_t packet_id, void (*callback)()) : id(packet_id), callback(callback) { - Order::orders[id] = this; - Packet::packets[id] = this; - } - - void set_callback(void (*callback)()) override { this->callback = callback; } - - void process() override { - if (callback != nullptr) - callback(); - string aux = tx_message; - tx_message = rx_message; - for (OrderProtocol* socket : OrderProtocol::sockets) { - if (socket == received_socket) - continue; - socket->send_order(*this); - } - tx_message = aux; - } - - uint8_t* build() { - if (buffer != nullptr) - free(buffer); - buffer = (uint8_t*)malloc(get_size()); - - memcpy(buffer, &id, sizeof(id)); - memcpy(buffer + sizeof(id), &tx_message_size, sizeof(tx_message_size)); - memcpy( - buffer + sizeof(id) + sizeof(tx_message_size), - tx_message.c_str(), - tx_message.size() - ); - return buffer; - } - - void notify(string message) { - tx_message = message; - tx_message_size = message.size(); - notify(); - } - - void notify() { - if (tx_message.empty()) { - ErrorHandler("Cannot notify empty notification"); - return; - } - for (OrderProtocol* socket : OrderProtocol::sockets) { - socket->send_order(*this); - } - } - - void parse(OrderProtocol* socket, uint8_t* data) { - received_socket = socket; - char* temp = (char*)malloc(get_string_size(data)); - memcpy(temp, data + sizeof(id) + sizeof(message_size_t), get_string_size(data)); - rx_message = string(temp); - free(temp); - } - - size_t get_size() { - size = sizeof(id) + sizeof(tx_message_size) + tx_message_size; - return size; - } - - uint16_t get_id() { return id; } - - void set_pointer(size_t index, void* pointer) override { - ErrorHandler("Notification does not suport this method!"); - } - - ~Notification() { - if (buffer != nullptr) - free(buffer); - } - -private: - uint16_t get_string_size(uint8_t* buffer) { return *(uint16_t*)(buffer + sizeof(id)); } -}; diff --git a/Inc/ST-LIB_HIGH/Protections/Protection.hpp b/Inc/ST-LIB_HIGH/Protections/Protection.hpp index b8371ce4b..f5d0b3620 100644 --- a/Inc/ST-LIB_HIGH/Protections/Protection.hpp +++ b/Inc/ST-LIB_HIGH/Protections/Protection.hpp @@ -1,99 +1,529 @@ #pragma once +#include + #include "C++Utilities/CppUtils.hpp" -#include "ErrorHandler/ErrorHandler.hpp" -#include "Boundary.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionErrors.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionTypes.hpp" +#include "ST-LIB_HIGH/Protections/Rules.hpp" +#include "ST-LIB_HIGH/Protections/SampleSource.hpp" + +namespace Protections { + +namespace detail { + +template constexpr T zero_value() { return static_cast(0); } + +template constexpr T absolute_value(T value) { + if constexpr (std::is_floating_point_v) { + return static_cast(std::fabs(value)); + } else if constexpr (std::is_signed_v) { + return value < 0 ? static_cast(-value) : value; + } else { + return value; + } +} + +template constexpr bool is_below(T sample, T threshold) { return sample < threshold; } + +template constexpr bool is_above(T sample, T threshold) { return sample > threshold; } + +template constexpr bool is_equal_to(T lhs, T rhs) { return lhs == rhs; } + +template constexpr bool is_not_equal_to(T lhs, T rhs) { return !is_equal_to(lhs, rhs); } + +} // namespace detail + +class RuleStateTracker { +public: + constexpr RuleState previous_state() const { return last_state; } + + constexpr RuleEdge advance(RuleState current_state) { + RuleEdge edge = RuleEdge::NONE; + if (current_state == RuleState::FAULT && last_state != RuleState::FAULT) { + edge = RuleEdge::FAULT_RAISED; + } else if (current_state == RuleState::WARNING && last_state != RuleState::WARNING) { + edge = RuleEdge::WARNING_RAISED; + } else if (current_state == RuleState::NORMAL && last_state != RuleState::NORMAL) { + edge = RuleEdge::RECOVERED; + } + + last_state = current_state; + return edge; + } -class Protection { private: - char* name = nullptr; - vector> boundaries; - BoundaryInterface* fault_protection = nullptr; - static constexpr Protections::FaultType fault_type = Protections::FaultType::FAULT; - uint8_t triggered_protecions_idx[4]{}; - uint8_t triggered_oks_idx[4]{}; - uint64_t last_notify_tick{0}; + RuleState last_state{RuleState::NORMAL}; +}; +class RuleSnapshotBuilder { public: - const uint64_t get_last_notify_tick() const { return last_notify_tick; } - void update_last_notify_tick(uint64_t new_tick) { last_notify_tick = new_tick; } - vector> warnings_triggered; - vector> oks_triggered; - template < - class Type, - ProtectionType... Protector, - template - class Boundaries> - Protection(Type* src, Boundaries... protectors) { - (boundaries.push_back( - shared_ptr(new Boundary(src, protectors)) - ), - ...); - } - - void set_name(char* name) { this->name = name; } - - char* get_name() { return name; } - - Protections::FaultType check_state() { - uint8_t warning_count = 0; - uint8_t oks_count = 0; - // to save the index of the triggered warning - uint8_t idx = 0; - for (shared_ptr& bound : boundaries) { - auto fault_type = bound->check_bounds(); - idx++; - fault_protection = nullptr; - switch (fault_type) { - // in case a Protection has more than one boundary, give priority to fault messages - case Protections::FAULT: - fault_protection = bound.get(); - // adding the fault_protection to the vector is not desired, - // the fault signal should propagate as fast as possible - if (bound->warning_already_triggered) { + template + static RuleSnapshot single_threshold( + RuleKind kind, + T observed, + RuleState current_state, + RuleEdge edge, + RuleState previous_state, + T fault_threshold, + optional warning_threshold, + float time_window_s = 0.0f, + float sample_rate_hz = 0.0f + ) { + RuleSnapshot snapshot{}; + snapshot.kind = kind; + snapshot.sample_encoding = sample_encoding_for(); + snapshot.observed_value = to_numeric_value(observed); + snapshot.time_window_s = time_window_s; + snapshot.sample_rate_hz = sample_rate_hz; + snapshot.uses_warning_threshold = should_use_warning_threshold( + current_state, + edge, + previous_state, + warning_threshold.has_value() + ); + + snapshot.threshold_a = to_numeric_value( + snapshot.uses_warning_threshold ? warning_threshold.value_or(fault_threshold) + : fault_threshold + ); + return snapshot; + } + + template + static RuleSnapshot range( + T observed, + RuleState current_state, + RuleEdge edge, + RuleState previous_state, + const RangeRuleConfig& config + ) { + RuleSnapshot snapshot{}; + snapshot.kind = RuleKind::RANGE; + snapshot.sample_encoding = sample_encoding_for(); + snapshot.observed_value = to_numeric_value(observed); + snapshot.has_threshold_b = true; + snapshot.uses_warning_threshold = should_use_warning_threshold( + current_state, + edge, + previous_state, + config.low_warning.has_value() && config.high_warning.has_value() + ); + snapshot.threshold_a = to_numeric_value( + snapshot.uses_warning_threshold ? config.low_warning.value_or(config.low_fault) + : config.low_fault + ); + snapshot.threshold_b = to_numeric_value( + snapshot.uses_warning_threshold ? config.high_warning.value_or(config.high_fault) + : config.high_fault + ); + return snapshot; + } + +private: + static constexpr bool should_use_warning_threshold( + RuleState current_state, + RuleEdge edge, + RuleState previous_state, + bool has_warning_threshold + ) { + return has_warning_threshold && + (current_state == RuleState::WARNING || + (edge == RuleEdge::RECOVERED && previous_state == RuleState::WARNING)); + } +}; + +template struct BelowEvaluator { + static constexpr RuleState compute(const T& sample, const BelowRuleConfig& config) { + if (detail::is_below(sample, config.fault_threshold)) { + return RuleState::FAULT; + } + if (config.warning_threshold.has_value() && + detail::is_below(sample, config.warning_threshold.value())) { + return RuleState::WARNING; + } + return RuleState::NORMAL; + } +}; + +template struct AboveEvaluator { + static constexpr RuleState compute(const T& sample, const AboveRuleConfig& config) { + if (detail::is_above(sample, config.fault_threshold)) { + return RuleState::FAULT; + } + if (config.warning_threshold.has_value() && + detail::is_above(sample, config.warning_threshold.value())) { + return RuleState::WARNING; + } + return RuleState::NORMAL; + } +}; + +template struct RangeEvaluator { + static constexpr RuleState compute(const T& sample, const RangeRuleConfig& config) { + if (detail::is_below(sample, config.low_fault) || + detail::is_above(sample, config.high_fault)) { + return RuleState::FAULT; + } + if (config.low_warning.has_value() && config.high_warning.has_value() && + (detail::is_below(sample, config.low_warning.value()) || + detail::is_above(sample, config.high_warning.value()))) { + return RuleState::WARNING; + } + return RuleState::NORMAL; + } +}; + +template struct EqualsEvaluator { + static constexpr RuleState compute(const T& sample, const EqualsRuleConfig& config) { + return detail::is_equal_to(sample, config.expected) ? RuleState::FAULT + : RuleState::NORMAL; + } +}; + +template struct NotEqualsEvaluator { + static constexpr RuleState compute(const T& sample, const NotEqualsRuleConfig& config) { + return detail::is_not_equal_to(sample, config.expected) ? RuleState::FAULT + : RuleState::NORMAL; + } +}; + +template struct TimeAccumulationEvaluator { + static RuleState compute( + const T& sample, + const TimeAccumulationRuleConfig& config, + array& accumulation_window, + size_t configured_window_samples, + size_t& window_fill_count, + size_t& window_index, + T& rolling_sum, + T& average_value + ) { + const T magnitude = detail::absolute_value(sample); + const size_t window_samples = configured_window_samples == 0 ? 1 : configured_window_samples; + + if (window_fill_count < window_samples) { + rolling_sum += magnitude; + accumulation_window[window_fill_count] = magnitude; + window_fill_count++; + average_value = static_cast(rolling_sum / static_cast(window_fill_count)); + return RuleState::NORMAL; + } + + rolling_sum -= accumulation_window[window_index]; + accumulation_window[window_index] = magnitude; + rolling_sum += magnitude; + window_index = (window_index + 1) % window_samples; + average_value = static_cast(rolling_sum / static_cast(window_samples)); + + if (detail::is_above(average_value, config.fault_threshold)) { + return RuleState::FAULT; + } + if (config.warning_threshold.has_value() && + detail::is_above(average_value, config.warning_threshold.value())) { + return RuleState::WARNING; + } + return RuleState::NORMAL; + } +}; + +template struct BelowRule { + BelowRuleConfig config{}; + RuleStateTracker tracker{}; + + RuleEvaluation evaluate(const T& sample) { + const RuleState previous_state = tracker.previous_state(); + const RuleState state = BelowEvaluator::compute(sample, config); + const RuleEdge edge = tracker.advance(state); + return { + .state = state, + .edge = edge, + .snapshot = RuleSnapshotBuilder::single_threshold( + RuleKind::BELOW, + sample, + state, + edge, + previous_state, + config.fault_threshold, + config.warning_threshold + ), + }; + } +}; + +template struct AboveRule { + AboveRuleConfig config{}; + RuleStateTracker tracker{}; + + RuleEvaluation evaluate(const T& sample) { + const RuleState previous_state = tracker.previous_state(); + const RuleState state = AboveEvaluator::compute(sample, config); + const RuleEdge edge = tracker.advance(state); + return { + .state = state, + .edge = edge, + .snapshot = RuleSnapshotBuilder::single_threshold( + RuleKind::ABOVE, + sample, + state, + edge, + previous_state, + config.fault_threshold, + config.warning_threshold + ), + }; + } +}; + +template struct RangeRule { + RangeRuleConfig config{}; + RuleStateTracker tracker{}; + + RuleEvaluation evaluate(const T& sample) { + const RuleState previous_state = tracker.previous_state(); + const RuleState state = RangeEvaluator::compute(sample, config); + const RuleEdge edge = tracker.advance(state); + return { + .state = state, + .edge = edge, + .snapshot = + RuleSnapshotBuilder::range(sample, state, edge, previous_state, config), + }; + } +}; + +template struct EqualsRule { + EqualsRuleConfig config{}; + RuleStateTracker tracker{}; + + RuleEvaluation evaluate(const T& sample) { + const RuleState previous_state = tracker.previous_state(); + const RuleState state = EqualsEvaluator::compute(sample, config); + const RuleEdge edge = tracker.advance(state); + return { + .state = state, + .edge = edge, + .snapshot = RuleSnapshotBuilder::single_threshold( + RuleKind::EQUALS, + sample, + state, + edge, + previous_state, + config.expected, + optional{} + ), + }; + } +}; + +template struct NotEqualsRule { + NotEqualsRuleConfig config{}; + RuleStateTracker tracker{}; + + RuleEvaluation evaluate(const T& sample) { + const RuleState previous_state = tracker.previous_state(); + const RuleState state = NotEqualsEvaluator::compute(sample, config); + const RuleEdge edge = tracker.advance(state); + return { + .state = state, + .edge = edge, + .snapshot = RuleSnapshotBuilder::single_threshold( + RuleKind::NOT_EQUALS, + sample, + state, + edge, + previous_state, + config.expected, + optional{} + ), + }; + } +}; + +template struct TimeAccumulationRule { + explicit TimeAccumulationRule(TimeAccumulationRuleConfig config) : config(config) { + configured_window_samples = + static_cast(std::lround(config.time_window_s * config.sample_rate_hz)); + if (configured_window_samples == 0) { + configured_window_samples = 1; + } + } + + RuleEvaluation evaluate(const T& sample) { + const RuleState previous_state = tracker.previous_state(); + const RuleState state = TimeAccumulationEvaluator::compute( + sample, + config, + accumulation_window, + configured_window_samples, + window_fill_count, + window_index, + rolling_sum, + average_value + ); + const RuleEdge edge = tracker.advance(state); + return { + .state = state, + .edge = edge, + .snapshot = RuleSnapshotBuilder::single_threshold( + RuleKind::TIME_ACCUMULATION, + average_value, + state, + edge, + previous_state, + config.fault_threshold, + config.warning_threshold, + config.time_window_s, + config.sample_rate_hz + ), + }; + } + + TimeAccumulationRuleConfig config{}; + RuleStateTracker tracker{}; + size_t configured_window_samples{1}; + array accumulation_window{}; + size_t window_fill_count{0}; + size_t window_index{0}; + T rolling_sum{detail::zero_value()}; + T average_value{detail::zero_value()}; +}; + +template > +struct TimeAccumulationRuleSelector { + using type = std::monostate; +}; + +template struct TimeAccumulationRuleSelector { + using type = TimeAccumulationRule; +}; + +template +using TimeAccumulationRuleModel = typename TimeAccumulationRuleSelector::type; + +template +using RuleModel = variant< + BelowRule, + AboveRule, + RangeRule, + EqualsRule, + NotEqualsRule, + TimeAccumulationRuleModel>; + +template inline RuleModel make_rule_model(const RuleDefinition& definition) { + return visit( + [](const RuleConfig& config) -> RuleModel { + using ConfigType = std::remove_cvref_t; + if constexpr (std::same_as>) { + return BelowRule{.config = config}; + } else if constexpr (std::same_as>) { + return AboveRule{.config = config}; + } else if constexpr (std::same_as>) { + return RangeRule{.config = config}; + } else if constexpr (std::same_as>) { + return EqualsRule{.config = config}; + } else if constexpr (std::same_as>) { + return NotEqualsRule{.config = config}; + } else if constexpr (std::same_as>) { + if constexpr (FloatingSample) { + return TimeAccumulationRule{config}; } else { - bound->warning_already_triggered = true; - } - return Protections::FAULT; - case Protections::WARNING: - // warnings are non fatal, but we cannot waste time, we need to check if any - // faults were triggered - if (bound->warning_already_triggered) - break; - bound->warning_already_triggered = true; - triggered_protecions_idx[warning_count] = idx - 1; - warning_count++; - break; - case Protections::OK: - if (bound->warning_already_triggered) { - bound->back_to_normal = true; - } - bound->warning_already_triggered = false; - if (bound->back_to_normal && - bound->boundary_type_id != BoundaryInterface::INFO_WARNING_BOUNDARY_TYPE_ID) { - triggered_oks_idx[oks_count] = idx - 1; - oks_count++; - bound->back_to_normal = false; + std::unreachable(); } + } else { + std::unreachable(); + } + }, + definition + ); +} - break; - default: - ErrorHandler("INVALID Protection::STATE type"); - break; +template inline RuleEvaluation evaluate_rule(RuleModel& rule, const T& sample) { + return visit( + [&sample](auto& concrete_rule) -> RuleEvaluation { + using RuleType = std::remove_cvref_t; + if constexpr (std::same_as) { + std::unreachable(); + } else { + return concrete_rule.evaluate(sample); } + }, + rule + ); +} + +template class Protection { +public: + Protection(const char* name, SampleSource source) : name(name), source(source) {} + + const char* get_name() const { return name; } + void initialize() {} + + expected + add_rule(expected, RuleConfigError> definition) { + if (!definition.has_value()) { + return unexpected(ProtectionError::INVALID_RULE_CONFIGURATION); } - if (oks_count) { - for (uint8_t i = 0; i < oks_count; i++) { - oks_triggered.push_back(boundaries[triggered_oks_idx[i]]); - } + return add_rule(*definition); + } + + expected add_rule(const RuleDefinition& definition) { + if (rule_count >= Config::max_rules_per_protection) { + return unexpected(ProtectionError::RULE_CAPACITY_EXCEEDED); } - if (warning_count) { - for (uint8_t i = 0; i < warning_count; i++) { - warnings_triggered.push_back(boundaries[triggered_protecions_idx[i]]); + + rules[rule_count++].emplace(make_rule_model(definition)); + return {}; + } + + ProtectionEvaluation evaluate() { + ProtectionEvaluation evaluation{}; + const T sample = source.read(); + + for (size_t index = 0; index < rule_count; ++index) { + if (!rules[index].has_value()) { + continue; + } + + const RuleEvaluation rule_evaluation = evaluate_rule(*rules[index], sample); + if (rule_evaluation.edge != RuleEdge::NONE && + evaluation.event_count < evaluation.events.size()) { + evaluation.events[evaluation.event_count++] = { + rule_evaluation.state, + rule_evaluation.edge, + rule_evaluation.snapshot, + }; + } + + if (rule_evaluation.state == RuleState::FAULT && !evaluation.has_active_fault) { + evaluation.has_active_fault = true; + evaluation.active_fault_edge = rule_evaluation.edge; + evaluation.active_fault_snapshot = rule_evaluation.snapshot; + evaluation.aggregated_state = RuleState::FAULT; + continue; + } + + if (evaluation.aggregated_state != RuleState::FAULT && + rule_evaluation.state == RuleState::WARNING) { + evaluation.aggregated_state = RuleState::WARNING; } - return Protections::WARNING; } - return Protections::OK; + + return evaluation; } - friend class ProtectionManager; + + void clear_runtime_state() { last_fault_publish_tick = 0; } + + uint64_t get_last_fault_publish_tick() const { return last_fault_publish_tick; } + + void set_last_fault_publish_tick(uint64_t tick) { last_fault_publish_tick = tick; } + +private: + const char* name{nullptr}; + SampleSource source; + array>, Config::max_rules_per_protection> rules{}; + size_t rule_count{0}; + uint64_t last_fault_publish_tick{0}; }; + +} // namespace Protections diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionConcepts.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionConcepts.hpp new file mode 100644 index 000000000..d19be3cc3 --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionConcepts.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "C++Utilities/CppUtils.hpp" + +namespace Protections { + +template +using remove_cvref_t = std::remove_cvref_t; + +template +concept ArithmeticSample = + std::is_arithmetic_v> && !std::same_as, long double>; + +template concept FloatingSample = std::floating_point>; + +template +concept ComparableSample = + ArithmeticSample && requires(remove_cvref_t lhs, remove_cvref_t rhs) { + { lhs < rhs } -> std::convertible_to; + { lhs > rhs } -> std::convertible_to; + }; + +template +concept EqualityComparableSample = + ArithmeticSample && std::equality_comparable>; + +template +concept SupportedProtectionSample = + std::same_as, bool> || std::same_as, int8_t> || + std::same_as, uint8_t> || std::same_as, int16_t> || + std::same_as, uint16_t> || std::same_as, int32_t> || + std::same_as, uint32_t> || std::same_as, int64_t> || + std::same_as, uint64_t> || std::same_as, float> || + std::same_as, double>; + +template concept ProtectionSample = ArithmeticSample && SupportedProtectionSample; + +template +concept ReadableSampleSource = + requires(const remove_cvref_t& source) { + typename remove_cvref_t::value_type; + requires ProtectionSample::value_type>; + { source.read() } -> std::convertible_to::value_type>; + }; + +} // namespace Protections diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp new file mode 100644 index 000000000..68528197a --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" +#include "ST-LIB_HIGH/Protections/FaultController.hpp" +#include "ST-LIB_HIGH/Protections/Protection.hpp" + +namespace Protections { + +template class ProtectionHandle { +public: + explicit ProtectionHandle(Protection* protection = nullptr) : protection(protection) {} + + expected + add_rule(expected, RuleConfigError> definition) { + if (protection == nullptr) { + return unexpected(ProtectionError::INVALID_HANDLE); + } + return protection->add_rule(std::move(definition)); + } + + expected add_rule(const RuleDefinition& definition) { + if (protection == nullptr) { + return unexpected(ProtectionError::INVALID_HANDLE); + } + return protection->add_rule(definition); + } + +private: + Protection* protection{nullptr}; +}; + +using ProtectionVariant = variant< + Protection, + Protection, + Protection, + Protection, + Protection, + Protection, + Protection, + Protection, + Protection, + Protection, + Protection>; + +} // namespace Protections + +class ProtectionEngine { +public: + using state_id = FaultController::state_id; + + template + static expected< + Protections::ProtectionHandle::value_type>, + Protections::ProtectionError> + create_protection(const char* name, Source source) { + using SampleType = typename std::remove_cvref_t::value_type; + + if (registration_locked) { + return unexpected(Protections::ProtectionError::REGISTRATION_LOCKED); + } + if (protection_count >= Protections::Config::max_protections) { + return unexpected(Protections::ProtectionError::PROTECTION_CAPACITY_EXCEEDED); + } + + auto& slot = protections[protection_count++]; + slot = Protections::ProtectionVariant( + std::in_place_type>, + name, + source + ); + auto* protection = std::get_if>(&slot.value()); + return Protections::ProtectionHandle{protection}; + } + + static void initialize(); + static void evaluate(); + static void link_state_machine(IStateMachine& general_state_machine, state_id fault_id); + static void clear_for_testing(); + +private: + template + static void publish_fault_if_due( + Protection& protection, + const Protections::ProtectionEvaluation& evaluation + ); + + template + static void publish_edge_events( + Protection& protection, + const Protections::ProtectionEvaluation& evaluation + ); + + static array, Protections::Config::max_protections> + protections; + static size_t protection_count; + static bool registration_locked; +}; diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp new file mode 100644 index 000000000..64b6bffa2 --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "C++Utilities/CppUtils.hpp" + +namespace Protections { + +enum class RuleConfigError : uint8_t { + INVALID_WARNING_THRESHOLD = 0, + INVALID_RANGE_THRESHOLDS, + INVALID_WINDOW, + INVALID_SAMPLE_RATE, + WINDOW_CAPACITY_EXCEEDED, +}; + +enum class ProtectionError : uint8_t { + INVALID_HANDLE = 0, + INVALID_RULE_CONFIGURATION, + RULE_CAPACITY_EXCEEDED, + PROTECTION_CAPACITY_EXCEEDED, + REGISTRATION_LOCKED, +}; + +} // namespace Protections diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionManager.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionManager.hpp deleted file mode 100644 index ec0459073..000000000 --- a/Inc/ST-LIB_HIGH/Protections/ProtectionManager.hpp +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -#include - -#include "C++Utilities/CppUtils.hpp" -#include "HALAL/Models/BoardID/BoardID.hpp" -#include "HALAL/Models/Packets/Order.hpp" -#include "Notification.hpp" -#include "Protection.hpp" -#include "StateMachine/StateMachine.hpp" - -#define getname(var) #var -#define add_protection(src, ...) \ - { \ - Protection& ref = ProtectionManager::_add_protection(src, __VA_ARGS__); \ - if (getname(src)[0] == '&') { \ - ref.set_name((char*)malloc(sizeof(getname(src)) - 1)); \ - sprintf(ref.get_name(), "%s", getname(src) + 1); \ - } else { \ - ref.set_name((char*)malloc(sizeof(getname(src)))); \ - sprintf(ref.get_name(), "%s", getname(src)); \ - } \ - } - -#define add_high_frequency_protection(src, ...) \ - { \ - Protection& ref = ProtectionManager::_add_high_frequency_protection(src, __VA_ARGS__); \ - if (getname(src)[0] == '&') { \ - ref.set_name((char*)malloc(sizeof(getname(src)) - 1)); \ - sprintf(ref.get_name(), "%s", getname(src) + 1); \ - } else { \ - ref.set_name((char*)malloc(sizeof(getname(src)))); \ - sprintf(ref.get_name(), "%s", getname(src)); \ - } \ - } - -class ProtectionManager { -public: - typedef uint8_t state_id; - static bool external_trigger; - - static const uint64_t notify_delay_in_microseconds = 2'000'000; - static uint64_t last_notify; - - static void set_id(Boards::ID id); - - static void link_state_machine(IStateMachine& general_state_machine, state_id fault_id); - - template < - class Type, - ProtectionType... Protector, - template - class Boundaries> - static Protection& _add_protection(Type* src, Boundaries... protectors) { - low_frequency_protections.push_back(Protection(src, protectors...)); - return low_frequency_protections.back(); - } - - template < - class Type, - ProtectionType... Protector, - template - class Boundaries> - static Protection& - _add_high_frequency_protection(Type* src, Boundaries... protectors) { - high_frequency_protections.push_back(Protection(src, protectors...)); - return high_frequency_protections.back(); - } - /** - * @brief call on startup to initialize the names of the protections - */ - static void initialize(); - static void add_standard_protections(); - static void check_protections(); - static void check_high_frequency_protections(); - static void warn(string message); - static void fault_and_propagate(); - static void propagate_fault(); - static void notify(Protection& protection); - -private: - static constexpr uint16_t warning_id = 2; - static constexpr uint16_t fault_id = 3; - static char* message; - static size_t message_size; - static bool test_fault; - static constexpr const char* format = "{\"boardId\": %s, \"timestamp\":{%s}, %s}"; - - static Boards::ID board_id; - static vector low_frequency_protections; - static vector high_frequency_protections; - static IStateMachine* general_state_machine; - static state_id fault_state_id; - - static Notification fault_notification; - static Notification warning_notification; - static StackOrder<0> fault_order; - - static void tcp_to_fault(); - static void to_fault(); - static void external_to_fault(); -}; diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionTypes.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionTypes.hpp new file mode 100644 index 000000000..490c466b4 --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionTypes.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include "C++Utilities/CppUtils.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionConcepts.hpp" + +namespace Protections { + +namespace Config { +inline constexpr size_t max_protections = 32; +inline constexpr size_t max_rules_per_protection = 16; +inline constexpr size_t max_time_accumulation_samples = 128; +inline constexpr uint64_t notify_delay_in_microseconds = 2'000'000ULL; +inline constexpr size_t max_name_length = 48; +} // namespace Config + +enum class RuleKind : uint8_t { + BELOW = 0, + ABOVE, + RANGE, + EQUALS, + NOT_EQUALS, + TIME_ACCUMULATION, +}; + +enum class RuleState : uint8_t { NORMAL = 0, WARNING, FAULT }; + +enum class RuleEdge : uint8_t { NONE = 0, WARNING_RAISED, FAULT_RAISED, RECOVERED }; + +enum class SampleEncoding : uint8_t { BOOL = 0, SIGNED, UNSIGNED, FLOAT32, FLOAT64 }; + +struct NumericValue { + union { + bool bool_value; + int64_t signed_value; + uint64_t unsigned_value; + float float32_value; + double float64_value; + }; + + constexpr NumericValue() : unsigned_value(0) {} +}; + +struct RuleSnapshot { + RuleKind kind{RuleKind::BELOW}; + SampleEncoding sample_encoding{SampleEncoding::SIGNED}; + NumericValue observed_value{}; + NumericValue threshold_a{}; + NumericValue threshold_b{}; + bool has_threshold_b{false}; + bool uses_warning_threshold{false}; + float time_window_s{0.0f}; + float sample_rate_hz{0.0f}; +}; + +struct RuleEvaluation { + RuleState state{RuleState::NORMAL}; + RuleEdge edge{RuleEdge::NONE}; + RuleSnapshot snapshot{}; +}; + +struct ProtectionEvent { + RuleState state{RuleState::NORMAL}; + RuleEdge edge{RuleEdge::NONE}; + RuleSnapshot snapshot{}; +}; + +struct ProtectionEvaluation { + RuleState aggregated_state{RuleState::NORMAL}; + bool has_active_fault{false}; + RuleEdge active_fault_edge{RuleEdge::NONE}; + RuleSnapshot active_fault_snapshot{}; + array events{}; + size_t event_count{0}; +}; + +template constexpr SampleEncoding sample_encoding_for() { + if constexpr (std::is_same_v) { + return SampleEncoding::BOOL; + } else if constexpr (std::is_floating_point_v) { + if constexpr (std::is_same_v) { + return SampleEncoding::FLOAT32; + } else { + return SampleEncoding::FLOAT64; + } + } else if constexpr (std::is_integral_v && std::is_signed_v) { + return SampleEncoding::SIGNED; + } + if constexpr (std::is_integral_v && std::is_unsigned_v) { + return SampleEncoding::UNSIGNED; + } + std::unreachable(); +} + +template constexpr NumericValue to_numeric_value(T value) { + NumericValue numeric{}; + if constexpr (std::is_same_v) { + numeric.bool_value = value; + } else if constexpr (std::is_floating_point_v) { + if constexpr (std::is_same_v) { + numeric.float32_value = value; + } else { + numeric.float64_value = value; + } + } else if constexpr (std::is_integral_v && std::is_signed_v) { + numeric.signed_value = static_cast(value); + } else if constexpr (std::is_integral_v && std::is_unsigned_v) { + numeric.unsigned_value = static_cast(value); + } else { + std::unreachable(); + } + return numeric; +} + +inline constexpr bool is_fault_state(RuleState state) { return state == RuleState::FAULT; } + +inline constexpr bool is_warning_state(RuleState state) { return state == RuleState::WARNING; } + +} // namespace Protections diff --git a/Inc/ST-LIB_HIGH/Protections/Rules.hpp b/Inc/ST-LIB_HIGH/Protections/Rules.hpp new file mode 100644 index 000000000..c7b1e593f --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/Rules.hpp @@ -0,0 +1,284 @@ +#pragma once + +#include "ST-LIB_HIGH/Protections/ProtectionConcepts.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionErrors.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionTypes.hpp" + +namespace Protections { + +template struct BelowRuleConfig { + T fault_threshold{}; + optional warning_threshold{}; +}; + +template struct AboveRuleConfig { + T fault_threshold{}; + optional warning_threshold{}; +}; + +template struct RangeRuleConfig { + T low_fault{}; + T high_fault{}; + optional low_warning{}; + optional high_warning{}; +}; + +template struct EqualsRuleConfig { + T expected{}; +}; + +template struct NotEqualsRuleConfig { + T expected{}; +}; + +template struct TimeAccumulationRuleConfig { + T fault_threshold{}; + optional warning_threshold{}; + float time_window_s{0.0f}; + float sample_rate_hz{0.0f}; +}; + +template +using RuleDefinition = variant< + BelowRuleConfig, + AboveRuleConfig, + RangeRuleConfig, + EqualsRuleConfig, + NotEqualsRuleConfig, + TimeAccumulationRuleConfig>; + +namespace detail { + +template +constexpr expected validate_runtime(Predicate predicate, Error error) { + if (!predicate()) { + return unexpected(error); + } + return {}; +} + +template +constexpr expected +validate_with_consteval(Predicate predicate, Error error, const char* message) { + if consteval { + if (!predicate()) { + (void)message; + return unexpected(error); + } + } + return validate_runtime(predicate, error); +} + +template +constexpr expected, RuleConfigError> +validate_below(optional warning_threshold, T fault_threshold) { + if (!warning_threshold.has_value()) { + return RuleDefinition{BelowRuleConfig{.fault_threshold = fault_threshold}}; + } + + const auto validation = validate_with_consteval( + [&] { return warning_threshold.value() >= fault_threshold; }, + RuleConfigError::INVALID_WARNING_THRESHOLD, + "below warning threshold must be above or equal to the fault threshold" + ); + if (!validation.has_value()) { + return unexpected(validation.error()); + } + + return RuleDefinition{BelowRuleConfig{ + .fault_threshold = fault_threshold, + .warning_threshold = warning_threshold, + }}; +} + +template +constexpr expected, RuleConfigError> +validate_above(optional warning_threshold, T fault_threshold) { + if (!warning_threshold.has_value()) { + return RuleDefinition{AboveRuleConfig{.fault_threshold = fault_threshold}}; + } + + const auto validation = validate_with_consteval( + [&] { return warning_threshold.value() <= fault_threshold; }, + RuleConfigError::INVALID_WARNING_THRESHOLD, + "above warning threshold must be below or equal to the fault threshold" + ); + if (!validation.has_value()) { + return unexpected(validation.error()); + } + + return RuleDefinition{AboveRuleConfig{ + .fault_threshold = fault_threshold, + .warning_threshold = warning_threshold, + }}; +} + +template +constexpr expected, RuleConfigError> validate_range( + T low_fault, + T high_fault, + optional low_warning, + optional high_warning +) { + const auto fault_validation = validate_with_consteval( + [&] { return low_fault <= high_fault; }, + RuleConfigError::INVALID_RANGE_THRESHOLDS, + "range low fault threshold must be below or equal to high fault threshold" + ); + if (!fault_validation.has_value()) { + return unexpected(fault_validation.error()); + } + + if (low_warning.has_value() != high_warning.has_value()) { + return unexpected(RuleConfigError::INVALID_RANGE_THRESHOLDS); + } + + if (low_warning.has_value()) { + const auto warning_validation = validate_with_consteval( + [&] { + return low_fault <= low_warning.value() && low_warning.value() <= high_warning.value() && + high_warning.value() <= high_fault; + }, + RuleConfigError::INVALID_RANGE_THRESHOLDS, + "range warning thresholds must stay inside the fault range" + ); + if (!warning_validation.has_value()) { + return unexpected(warning_validation.error()); + } + } + + return RuleDefinition{RangeRuleConfig{ + .low_fault = low_fault, + .high_fault = high_fault, + .low_warning = low_warning, + .high_warning = high_warning, + }}; +} + +template +constexpr expected, RuleConfigError> validate_time_accumulation( + T fault_threshold, + optional warning_threshold, + float window_seconds, + float sample_rate_hz +) { + const auto window_validation = validate_with_consteval( + [&] { return window_seconds > 0.0f; }, + RuleConfigError::INVALID_WINDOW, + "time_accumulation requires a positive window" + ); + if (!window_validation.has_value()) { + return unexpected(window_validation.error()); + } + + const auto rate_validation = validate_with_consteval( + [&] { return sample_rate_hz > 0.0f; }, + RuleConfigError::INVALID_SAMPLE_RATE, + "time_accumulation requires a positive sample rate" + ); + if (!rate_validation.has_value()) { + return unexpected(rate_validation.error()); + } + + const auto window_samples = static_cast(std::lround(window_seconds * sample_rate_hz)); + if (window_samples == 0) { + return unexpected(RuleConfigError::INVALID_WINDOW); + } + if (window_samples > Config::max_time_accumulation_samples) { + return unexpected(RuleConfigError::WINDOW_CAPACITY_EXCEEDED); + } + + if (warning_threshold.has_value()) { + const auto warning_validation = validate_with_consteval( + [&] { return warning_threshold.value() <= fault_threshold; }, + RuleConfigError::INVALID_WARNING_THRESHOLD, + "time_accumulation warning threshold must be below or equal to the fault threshold" + ); + if (!warning_validation.has_value()) { + return unexpected(warning_validation.error()); + } + } + + return RuleDefinition{TimeAccumulationRuleConfig{ + .fault_threshold = fault_threshold, + .warning_threshold = warning_threshold, + .time_window_s = window_seconds, + .sample_rate_hz = sample_rate_hz, + }}; +} + +} // namespace detail + +namespace Rules { + +template +constexpr expected, RuleConfigError> below(T fault_threshold) { + return detail::validate_below(nullopt, fault_threshold); +} + +template +constexpr expected, RuleConfigError> +below(T fault_threshold, T warning_threshold) { + return detail::validate_below(warning_threshold, fault_threshold); +} + +template +constexpr expected, RuleConfigError> above(T fault_threshold) { + return detail::validate_above(nullopt, fault_threshold); +} + +template +constexpr expected, RuleConfigError> +above(T fault_threshold, T warning_threshold) { + return detail::validate_above(warning_threshold, fault_threshold); +} + +template +constexpr expected, RuleConfigError> range(T low_fault, T high_fault) { + return detail::validate_range(low_fault, high_fault, nullopt, nullopt); +} + +template +constexpr expected, RuleConfigError> +range(T low_fault, T high_fault, T low_warning, T high_warning) { + return detail::validate_range(low_fault, high_fault, low_warning, high_warning); +} + +template +constexpr expected, RuleConfigError> equals(T value) { + return RuleDefinition{EqualsRuleConfig{.expected = value}}; +} + +template +constexpr expected, RuleConfigError> not_equals(T value) { + return RuleDefinition{NotEqualsRuleConfig{.expected = value}}; +} + +template +constexpr expected, RuleConfigError> +time_accumulation(T fault_threshold, float window_seconds, float sample_rate_hz) { + return detail::validate_time_accumulation( + fault_threshold, + nullopt, + window_seconds, + sample_rate_hz + ); +} + +template +constexpr expected, RuleConfigError> time_accumulation( + T fault_threshold, + T warning_threshold, + float window_seconds, + float sample_rate_hz +) { + return detail::validate_time_accumulation( + fault_threshold, + warning_threshold, + window_seconds, + sample_rate_hz + ); +} + +} // namespace Rules +} // namespace Protections diff --git a/Inc/ST-LIB_HIGH/Protections/SampleSource.hpp b/Inc/ST-LIB_HIGH/Protections/SampleSource.hpp new file mode 100644 index 000000000..c35050a51 --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/SampleSource.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "C++Utilities/CppUtils.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionConcepts.hpp" + +template class SampleSource { +public: + using value_type = T; + + explicit constexpr SampleSource(T& value) : value_ptr(&value) {} + explicit constexpr SampleSource(T* value_ptr) : value_ptr(value_ptr) {} + + constexpr const T& read() const { return *value_ptr; } + constexpr T* raw() const { return value_ptr; } + +private: + T* value_ptr{nullptr}; +}; diff --git a/Inc/ST-LIB_HIGH/ST-LIB_HIGH.hpp b/Inc/ST-LIB_HIGH/ST-LIB_HIGH.hpp index a74d4098f..3b7c5609e 100644 --- a/Inc/ST-LIB_HIGH/ST-LIB_HIGH.hpp +++ b/Inc/ST-LIB_HIGH/ST-LIB_HIGH.hpp @@ -7,8 +7,10 @@ #pragma once +#include "Protections/ProtectionEngine.hpp" +#include "Protections/Rules.hpp" +#include "Protections/SampleSource.hpp" #include "Protections/Protection.hpp" -#include "Protections/ProtectionManager.hpp" #include "Control/ControlBlock.hpp" #include "Control/FeedbackControlBlock.hpp" #include "Control/SplitterBlock.hpp" diff --git a/Src/ST-LIB_HIGH/Protections/Boundary.cpp b/Src/ST-LIB_HIGH/Protections/Boundary.cpp deleted file mode 100644 index 3c9f6847c..000000000 --- a/Src/ST-LIB_HIGH/Protections/Boundary.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "ST-LIB_HIGH/Protections/Boundary.hpp" - -const map BoundaryInterface::format_look_up{ - {type_id, 0}, - {type_id, 1}, - {type_id, 2}, - {type_id, 3}, - {type_id, 4}, - {type_id, 5}, - {type_id, 6}, - {type_id, 7}, - {type_id, 8}, - {type_id, 9}, - {type_id, 10}, - {type_id, 11}, -}; diff --git a/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp b/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp new file mode 100644 index 000000000..8c6f3c6d6 --- /dev/null +++ b/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp @@ -0,0 +1,105 @@ +#include "ST-LIB_HIGH/Protections/ProtectionEngine.hpp" + +#include "HALAL/Services/Time/RTC.hpp" +#include "HALAL/Services/Time/Scheduler.hpp" + +array, Protections::Config::max_protections> + ProtectionEngine::protections = {}; +size_t ProtectionEngine::protection_count = 0; +bool ProtectionEngine::registration_locked = false; + +void ProtectionEngine::initialize() { +#if defined(HAL_RTC_MODULE_ENABLED) && !defined(SIM_ON) + Global_RTC::ensure_started(); +#endif + + registration_locked = true; + for (size_t protection_index = 0; protection_index < protection_count; protection_index++) { + if (protections[protection_index].has_value()) { + visit([](auto& protection) { protection.initialize(); }, *protections[protection_index]); + } + } +} + +void ProtectionEngine::link_state_machine( + IStateMachine& general_state_machine, + ProtectionEngine::state_id fault_id +) { + FaultController::link_state_machine(general_state_machine, fault_id); +} + +template +void ProtectionEngine::publish_fault_if_due( + Protection& protection, + const Protections::ProtectionEvaluation& evaluation +) { + if (!evaluation.has_active_fault) { + return; + } + + const uint64_t tick = Scheduler::get_global_tick(); + const uint64_t last_publish_tick = protection.get_last_fault_publish_tick(); + if (last_publish_tick != 0 && + tick < last_publish_tick + Protections::Config::notify_delay_in_microseconds) { + return; + } + + Diagnostics::Hub::publish_protection_event( + protection.get_name(), + Protections::RuleState::FAULT, + evaluation.active_fault_edge, + evaluation.active_fault_snapshot + ); + protection.set_last_fault_publish_tick(tick); +} + +template +void ProtectionEngine::publish_edge_events( + Protection& protection, + const Protections::ProtectionEvaluation& evaluation +) { + for (size_t event_index = 0; event_index < evaluation.event_count; event_index++) { + const Protections::ProtectionEvent& event = evaluation.events[event_index]; + if (event.state == Protections::RuleState::FAULT) { + continue; + } + Diagnostics::Hub::publish_protection_event( + protection.get_name(), + event.state, + event.edge, + event.snapshot + ); + } +} + +void ProtectionEngine::evaluate() { + for (size_t protection_index = 0; protection_index < protection_count; protection_index++) { + if (!protections[protection_index].has_value()) { + continue; + } + + visit( + [](auto& protection) { + const Protections::ProtectionEvaluation evaluation = protection.evaluate(); + publish_edge_events(protection, evaluation); + + if (evaluation.has_active_fault) { + publish_fault_if_due(protection, evaluation); + FaultController::enter_fault(); + } + }, + *protections[protection_index] + ); + } +} + +void ProtectionEngine::clear_for_testing() { + for (size_t protection_index = 0; protection_index < protection_count; protection_index++) { + if (protections[protection_index].has_value()) { + visit([](auto& protection) { protection.clear_runtime_state(); }, *protections[protection_index]); + protections[protection_index].reset(); + } + } + protection_count = 0; + registration_locked = false; +} diff --git a/Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp b/Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp deleted file mode 100644 index 6ca063ffa..000000000 --- a/Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#include "Protections/ProtectionManager.hpp" - -#include "HALAL/Services/Communication/FDCAN/FDCAN.hpp" -#include "HALAL/Services/Time/Scheduler.hpp" - -#include "Protections/Notification.hpp" - -IStateMachine* ProtectionManager::general_state_machine = nullptr; -Notification ProtectionManager::fault_notification = {ProtectionManager::fault_id, nullptr}; -Notification ProtectionManager::warning_notification = {ProtectionManager::warning_id, nullptr}; -StackOrder<0> ProtectionManager::fault_order(Protections::FAULT, external_to_fault); -uint64_t ProtectionManager::last_notify = 0; -bool ProtectionManager::external_trigger = false; -bool ProtectionManager::test_fault = false; -void* error_handler; -void* info_warning; - -void ProtectionManager::initialize() { - Global_RTC::ensure_started(); - for (Protection& protection : low_frequency_protections) { - for (auto& boundary : protection.boundaries) { - boundary->update_name(protection.get_name()); - } - } - for (Protection& protection : high_frequency_protections) { - for (auto& boundary : protection.boundaries) { - boundary->update_name(protection.get_name()); - } - } -} - -void ProtectionManager::add_standard_protections() { - add_protection(error_handler, Boundary(error_handler)); - add_protection(info_warning, Boundary(info_warning)); -} - -void ProtectionManager::set_id(Boards::ID board_id) { ProtectionManager::board_id = board_id; } - -void ProtectionManager::link_state_machine( - IStateMachine& general_state_machine, - state_id fault_id -) { - ProtectionManager::general_state_machine = &general_state_machine; - ProtectionManager::fault_state_id = fault_id; -} - -void ProtectionManager::tcp_to_fault() { - test_fault = true; - to_fault(); -} - -void ProtectionManager::to_fault() { - if (general_state_machine->get_current_state_id() != fault_state_id) { - fault_and_propagate(); - } -} - -void ProtectionManager::external_to_fault() { - if (general_state_machine->get_current_state_id() != fault_state_id) { - external_trigger = true; - fault_and_propagate(); - } -} - -void ProtectionManager::fault_and_propagate() { - ProtectionManager::general_state_machine->force_change_state(fault_state_id); - propagate_fault(); -} - -void ProtectionManager::check_protections() { - for (Protection& protection : low_frequency_protections) { - auto protection_status = protection.check_state(); - - if (general_state_machine == nullptr) { - ErrorHandler("Protection Manager does not have General State Machine " - "Linked"); - return; - } - // ensure we only go to FAULT if a FAULT was triggered, and not only a - // WARNING - if (protection.fault_type == Protections::FAULT && - protection_status == Protections::FAULT) { - ProtectionManager::to_fault(); - } - Global_RTC::update_rtc_data(); - if (Scheduler::get_global_tick() > - protection.get_last_notify_tick() + notify_delay_in_microseconds) { - ProtectionManager::notify(protection); - protection.update_last_notify_tick(Scheduler::get_global_tick()); - } - } -} - -void ProtectionManager::check_high_frequency_protections() { - for (Protection& protection : high_frequency_protections) { - auto protection_status = protection.check_state(); - - if (general_state_machine == nullptr) { - ErrorHandler("Protection Manager does not have General State Machine " - "Linked"); - return; - } - - if (protection.fault_type == Protections::FAULT && - protection_status == Protections::FAULT) { - ProtectionManager::to_fault(); - } - Global_RTC::update_rtc_data(); - if (Scheduler::get_global_tick() > - protection.get_last_notify_tick() + notify_delay_in_microseconds) { - ProtectionManager::notify(protection); - protection.update_last_notify_tick(Scheduler::get_global_tick()); - } - } -} - -void ProtectionManager::warn(string message) { warning_notification.notify(message); } - -void ProtectionManager::notify(Protection& protection) { - const bool is_error_handler_fault = - protection.fault_protection != nullptr && - protection.fault_protection->boundary_type_id == ERROR_HANDLER; - const bool should_send_fault = - protection.fault_protection != nullptr && - (!is_error_handler_fault || ErrorHandlerModel::error_to_communicate); - bool error_handler_delivered = false; - bool info_warning_delivered = false; - - for (OrderProtocol* socket : OrderProtocol::sockets) { - if (should_send_fault) { - if (is_error_handler_fault) { - protection.fault_protection->update_error_handler_message( - protection.fault_protection->get_error_handler_string() - ); - error_handler_delivered = - socket->send_order(*protection.fault_protection->fault_message) || - error_handler_delivered; - } else { - socket->send_order(*protection.fault_protection->fault_message); - } - } - for (auto& warning : protection.warnings_triggered) { - if (warning->boundary_type_id == BoundaryInterface::INFO_WARNING_BOUNDARY_TYPE_ID) { - if (!InfoWarning::warning_to_communicate) { - continue; - } - warning->update_warning_message(warning->get_warning_string()); - info_warning_delivered = - socket->send_order(*warning->warn_message) || info_warning_delivered; - continue; - } - socket->send_order(*warning->warn_message); - } - for (auto& ok : protection.oks_triggered) { - socket->send_order(*ok->ok_message); - } - } - - if (error_handler_delivered) { - ErrorHandlerModel::error_to_communicate = false; - } - - if (info_warning_delivered) { - InfoWarning::warning_triggered = false; - InfoWarning::warning_to_communicate = false; - } - - protection.oks_triggered.clear(); - protection.warnings_triggered.clear(); -} - -void ProtectionManager::propagate_fault() { - for (OrderProtocol* socket : OrderProtocol::sockets) { - socket->send_order(ProtectionManager::fault_order); - } - for (const auto& [key, value] : FDCAN::registered_fdcan) { - FDCAN::transmit(key, FDCAN::ID::FAULT_ID, NULL); - } -} - -Boards::ID ProtectionManager::board_id = Boards::ID::NOBOARD; -size_t ProtectionManager::message_size = 0; -char* ProtectionManager::message = nullptr; -ProtectionManager::state_id ProtectionManager::fault_state_id = 255; -vector ProtectionManager::low_frequency_protections = {}; -vector ProtectionManager::high_frequency_protections = {}; diff --git a/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp b/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp index d67acbb21..daa2ffa80 100644 --- a/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp +++ b/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp @@ -8,5 +8,5 @@ #include "ST-LIB_HIGH.hpp" void STLIB_HIGH::start() { - // ProtectionManager::add_standard_protections(); + ProtectionEngine::initialize(); } From eb732a8bfce64592e9c65873276e1636441da042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Sun, 5 Apr 2026 11:36:07 +0200 Subject: [PATCH 02/23] Decouple diagnostics and fault broadcasting --- CMakeLists.txt | 14 +- .../Communication/Ethernet/NewEthernet.hpp | 2 - .../Services/Diagnostics/Diagnostics.hpp | 183 +++++++++++ .../Services/InfoWarning/InfoWarning.hpp | 14 +- .../Protections/FaultController.hpp | 75 +++++ Inc/ST-LIB_HIGH/Protections/FaultRuntime.hpp | 10 + Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp | 14 +- .../Diagnostics/DiagnosticFormatter.cpp | 273 ++++++++++++++++ .../Services/Diagnostics/DiagnosticSinks.cpp | 216 +++++++++++++ .../DiagnosticTimestampProvider.cpp | 36 +++ .../Services/Diagnostics/DiagnosticsHub.cpp | 214 ++++++++++++ .../Services/InfoWarning/InfoWarning.cpp | 301 ++--------------- Src/ST-LIB.cpp | 8 +- .../Protections/FaultController.cpp | 55 ++++ Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp | 68 ++++ Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp | 305 ++---------------- Tests/CMakeLists.txt | 11 + Tests/Time/common_tests.cpp | 31 +- Tests/diagnostics_test.cpp | 228 +++++++++++++ 19 files changed, 1454 insertions(+), 604 deletions(-) create mode 100644 Inc/HALAL/Services/Diagnostics/Diagnostics.hpp create mode 100644 Inc/ST-LIB_HIGH/Protections/FaultController.hpp create mode 100644 Inc/ST-LIB_HIGH/Protections/FaultRuntime.hpp create mode 100644 Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp create mode 100644 Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp create mode 100644 Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp create mode 100644 Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp create mode 100644 Src/ST-LIB_HIGH/Protections/FaultController.cpp create mode 100644 Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp create mode 100644 Tests/diagnostics_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 409c22148..3162bc342 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,6 +277,7 @@ set(HALAL_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/LowPowerTimer/LowPowerTimer.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MDMA/MDMA.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MPUManager/MPUManager.cpp + ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/Packets/Packet.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/Packets/SPIOrder.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/SPI/SPI2.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/DMA/DMA2.cpp @@ -289,6 +290,10 @@ set(HALAL_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Communication/UART/UART.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/DigitalInputService/DigitalInputService.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/DigitalOutputService/DigitalOutputService.cpp + ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp + ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp + ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp + ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/EXTI/EXTI.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/FMAC/FMAC.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Flash/Flash.cpp @@ -306,7 +311,6 @@ set(HALAL_C_ETH_CORE) set(HALAL_CPP_ETH_CORE ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/IPV4/IPV4.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MAC/MAC.cpp - ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/Packets/Packet.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Communication/SNTP/SNTP.cpp ) @@ -380,16 +384,16 @@ set(STLIB_LOW_CPP_NO_ETH set(STLIB_HIGH_C_ETH) -set(STLIB_HIGH_CPP_ETH - ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/Boundary.cpp - ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/ProtectionManager.cpp -) +set(STLIB_HIGH_CPP_ETH) set(STLIB_HIGH_C_NO_ETH) set(STLIB_HIGH_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashStorer.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashVariable.cpp + ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/FaultController.cpp + ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp + ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp ) diff --git a/Inc/HALAL/Services/Communication/Ethernet/NewEthernet.hpp b/Inc/HALAL/Services/Communication/Ethernet/NewEthernet.hpp index b99434e35..4f44cde8e 100644 --- a/Inc/HALAL/Services/Communication/Ethernet/NewEthernet.hpp +++ b/Inc/HALAL/Services/Communication/Ethernet/NewEthernet.hpp @@ -247,8 +247,6 @@ struct EthernetDomain { void update() { ethernetif_input(&gnetif); sys_check_timeouts(); - ErrorHandlerModel::ErrorHandlerUpdate(); - InfoWarning::InfoWarningUpdate(); if (HAL_GetTick() - EthernetLinkTimer >= 100) { EthernetLinkTimer = HAL_GetTick(); diff --git a/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp new file mode 100644 index 000000000..85ed8577c --- /dev/null +++ b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp @@ -0,0 +1,183 @@ +#pragma once + +#include "C++Utilities/CppUtils.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionTypes.hpp" + +namespace Diagnostics { + +namespace Config { +inline constexpr size_t max_sinks = 4; +inline constexpr size_t max_sink_storage = 1024; +inline constexpr size_t history_capacity = 16; +inline constexpr size_t pending_capacity = 16; +inline constexpr size_t origin_capacity = Protections::Config::max_name_length; +inline constexpr size_t runtime_message_capacity = 160; +inline constexpr size_t function_capacity = 64; +inline constexpr size_t file_capacity = 96; +inline constexpr size_t formatted_message_capacity = 320; +} // namespace Config + +enum class Severity : uint8_t { INFO = 0, WARNING, FAULT }; +enum class Category : uint8_t { RUNTIME_ERROR = 0, RUNTIME_WARNING, PROTECTION_EVENT }; +enum class RegistrationError : uint8_t { CAPACITY_EXCEEDED = 0, STORAGE_TOO_SMALL }; + +struct Timestamp { + bool has_rtc{false}; + uint16_t counter{0}; + uint8_t second{0}; + uint8_t minute{0}; + uint8_t hour{0}; + uint8_t day{0}; + uint8_t month{0}; + uint16_t year{0}; + uint64_t uptime_us{0}; +}; + +struct RuntimeDiagnosticPayload { + uint32_t line{0}; + bool truncated{false}; + char message[Config::runtime_message_capacity + 1]{}; + char function_name[Config::function_capacity + 1]{}; + char file_name[Config::file_capacity + 1]{}; +}; + +struct ProtectionDiagnosticPayload { + Protections::RuleKind rule_kind{Protections::RuleKind::BELOW}; + Protections::RuleState state{Protections::RuleState::NORMAL}; + Protections::RuleEdge edge{Protections::RuleEdge::NONE}; + Protections::SampleEncoding sample_encoding{Protections::SampleEncoding::SIGNED}; + Protections::NumericValue observed_value{}; + Protections::NumericValue threshold_a{}; + Protections::NumericValue threshold_b{}; + bool has_threshold_b{false}; + bool uses_warning_threshold{false}; + float time_window_s{0.0f}; + float sample_rate_hz{0.0f}; +}; + +union DiagnosticPayload { + RuntimeDiagnosticPayload runtime; + ProtectionDiagnosticPayload protection; + + constexpr DiagnosticPayload() : runtime{} {} +}; + +struct DiagnosticRecord { + Severity severity{Severity::INFO}; + Category category{Category::RUNTIME_WARNING}; + Timestamp timestamp{}; + char origin[Config::origin_capacity + 1]{}; + DiagnosticPayload payload{}; +}; + +class DiagnosticSink { +public: + virtual ~DiagnosticSink() = default; + virtual bool publish(const DiagnosticRecord& record) = 0; +}; + +class DiagnosticFormatter { +public: + static void describe(const DiagnosticRecord& record, char* buffer, size_t buffer_size); +}; + +class DiagnosticTimestampProvider { +public: + static Timestamp capture(); +}; + +class Hub { +public: + template + static expected emplace_sink(Args&&... args) { + if (sink_count >= Config::max_sinks) { + return unexpected(RegistrationError::CAPACITY_EXCEEDED); + } + if constexpr ( + sizeof(Sink) > Config::max_sink_storage || + alignof(Sink) > alignof(std::max_align_t)) { + return unexpected(RegistrationError::STORAGE_TOO_SMALL); + } else { + SinkStorage& slot = sink_storage[sink_count]; + auto* sink = construct_at( + reinterpret_cast(slot.bytes.data()), + std::forward(args)... + ); + slot.sink = sink; + slot.destroy = [](DiagnosticSink* base) { destroy_at(static_cast(base)); }; + sinks[sink_count++] = sink; + return sink; + } + } + + static void publish(DiagnosticRecord record); + static void publish_runtime_error( + const char* message, + bool truncated, + int line, + const char* func, + const char* file + ); + static void publish_runtime_warning( + const char* message, + bool truncated, + int line, + const char* func, + const char* file + ); + static void publish_protection_event( + const char* protection_name, + Protections::RuleState state, + Protections::RuleEdge edge, + const Protections::RuleSnapshot& snapshot + ); + static void flush(); + + static void clear_for_testing(); + static size_t history_size_for_testing(); + static size_t pending_size_for_testing(); + +private: + struct PendingRecord { + DiagnosticRecord record{}; + uint8_t delivered_mask{0}; + }; + + struct SinkStorage { + alignas(std::max_align_t) array bytes{}; + DiagnosticSink* sink{nullptr}; + void (*destroy)(DiagnosticSink*){nullptr}; + + void reset() { + if (sink != nullptr && destroy != nullptr) { + destroy(sink); + } + sink = nullptr; + destroy = nullptr; + } + }; + + static void push_history(const DiagnosticRecord& record); + static void push_pending(const DiagnosticRecord& record); + static void remove_pending(size_t index); + + static array sink_storage; + static array sinks; + static size_t sink_count; + static array history; + static size_t history_count; + static size_t history_next_index; + static array pending_records; + static size_t pending_count; +}; + +class Runtime { +public: + static void install_default_sinks(); + static void reset_for_testing(); + +private: + static bool defaults_installed; +}; + +} // namespace Diagnostics diff --git a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp index ad00091fe..cbd58ebf1 100644 --- a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp +++ b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp @@ -11,15 +11,11 @@ class InfoWarning { private: - static string description; - static string line; - static string func; - static string file; + static int line; + static const char* func; + static const char* file; public: - static bool warning_triggered; - static bool warning_to_communicate; - /** * @brief Triggers WarningHandler and format the warning message. The format works * exactly like printf format. @@ -27,7 +23,7 @@ class InfoWarning { * @param format String which will be formated. * @param args Arguments specifying data to print */ - static void InfoWarningTrigger(string format, ...); + static void InfoWarningTrigger(const char* format, ...); /** * @brief Get all metadata needed for the warning message, including the line function and file. @@ -48,8 +44,6 @@ class InfoWarning { * @brief Transmit the warning message. */ static void InfoWarningUpdate(); - - friend class BoundaryInterface; }; #define WARNING(x, ...) \ diff --git a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp new file mode 100644 index 000000000..b0047f5cb --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "C++Utilities/CppUtils.hpp" +#include "StateMachine/StateMachine.hpp" + +class FaultBroadcaster { +public: + virtual ~FaultBroadcaster() = default; + virtual bool broadcast_fault() = 0; +}; + +enum class FaultBroadcasterRegistrationError : uint8_t { + CAPACITY_EXCEEDED = 0, + STORAGE_TOO_SMALL, +}; + +class FaultController { +public: + using state_id = uint8_t; + static constexpr size_t max_broadcasters = 4; + static constexpr size_t max_broadcaster_storage = 512; + + template + static expected emplace_broadcaster( + Args&&... args + ) { + if (broadcaster_count >= max_broadcasters) { + return unexpected(FaultBroadcasterRegistrationError::CAPACITY_EXCEEDED); + } + if constexpr ( + sizeof(Broadcaster) > max_broadcaster_storage || + alignof(Broadcaster) > alignof(std::max_align_t)) { + return unexpected(FaultBroadcasterRegistrationError::STORAGE_TOO_SMALL); + } else { + BroadcasterStorage& slot = broadcaster_storage[broadcaster_count]; + auto* broadcaster = construct_at( + reinterpret_cast(slot.bytes.data()), + std::forward(args)... + ); + slot.broadcaster = broadcaster; + slot.destroy = [](FaultBroadcaster* base) { + destroy_at(static_cast(base)); + }; + broadcasters[broadcaster_count++] = broadcaster; + return broadcaster; + } + } + + static void link_state_machine(IStateMachine& general_state_machine, state_id fault_id); + static void enter_fault(); + static void enter_external_fault(); + + static void clear_broadcasters_for_testing(); + +private: + struct BroadcasterStorage { + alignas(std::max_align_t) array bytes{}; + FaultBroadcaster* broadcaster{nullptr}; + void (*destroy)(FaultBroadcaster*){nullptr}; + + void reset() { + if (broadcaster != nullptr && destroy != nullptr) { + destroy(broadcaster); + } + broadcaster = nullptr; + destroy = nullptr; + } + }; + + static IStateMachine* general_state_machine; + static state_id fault_state_id; + static array broadcaster_storage; + static array broadcasters; + static size_t broadcaster_count; +}; diff --git a/Inc/ST-LIB_HIGH/Protections/FaultRuntime.hpp b/Inc/ST-LIB_HIGH/Protections/FaultRuntime.hpp new file mode 100644 index 000000000..6c603668c --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/FaultRuntime.hpp @@ -0,0 +1,10 @@ +#pragma once + +class FaultRuntime { +public: + static void install_default_broadcasters(); + static void reset_for_testing(); + +private: + static bool defaults_installed; +}; diff --git a/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp b/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp index 297523087..0b87ddd8c 100644 --- a/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp +++ b/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp @@ -15,15 +15,11 @@ class ErrorHandlerModel { private: - static string description; - static string line; - static string func; - static string file; + static int line; + static const char* func; + static const char* file; public: - static double error_triggered; - static bool error_to_communicate; - /** * @brief Triggers ErrorHandler and format the error message. The format works * exactly like printf format. @@ -32,7 +28,7 @@ class ErrorHandlerModel { * @param args Arguments specifying data to print * @return uint8_t Id of the service. */ - static void ErrorHandlerTrigger(string format, ...); + static void ErrorHandlerTrigger(const char* format, ...); /** * @brief Get all metadata needed for the error message, including the line function and file. @@ -54,8 +50,6 @@ class ErrorHandlerModel { * @brief Transmit the error message. */ static void ErrorHandlerUpdate(); - - friend class BoundaryInterface; }; #define ErrorHandler(x, ...) \ diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp new file mode 100644 index 000000000..2860cd5b9 --- /dev/null +++ b/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp @@ -0,0 +1,273 @@ +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" + +namespace Diagnostics { + +namespace { + +size_t bounded_strnlen(const char* src, size_t max_length) { + if (src == nullptr) { + return 0; + } + + size_t length = 0; + while (length < max_length && src[length] != '\0') { + length++; + } + return length; +} + +void append_formatted(char* buffer, size_t buffer_size, const char* format, ...) { + if (buffer_size == 0) { + return; + } + + const size_t offset = bounded_strnlen(buffer, buffer_size - 1); + if (offset >= buffer_size - 1) { + buffer[buffer_size - 1] = '\0'; + return; + } + + va_list arguments; + va_start(arguments, format); + vsnprintf(buffer + offset, buffer_size - offset, format, arguments); + va_end(arguments); +} + +const char* rule_kind_label(Protections::RuleKind kind) { + switch (kind) { + case Protections::RuleKind::BELOW: + return "below"; + case Protections::RuleKind::ABOVE: + return "above"; + case Protections::RuleKind::RANGE: + return "range"; + case Protections::RuleKind::EQUALS: + return "equals"; + case Protections::RuleKind::NOT_EQUALS: + return "not_equals"; + case Protections::RuleKind::TIME_ACCUMULATION: + return "time_accumulation"; + } + std::unreachable(); +} + +const char* rule_state_label(Protections::RuleState state) { + switch (state) { + case Protections::RuleState::FAULT: + return "fault"; + case Protections::RuleState::WARNING: + return "warning"; + case Protections::RuleState::NORMAL: + return "normal"; + } + std::unreachable(); +} + +const char* rule_edge_label(Protections::RuleEdge edge) { + switch (edge) { + case Protections::RuleEdge::FAULT_RAISED: + return "fault_raised"; + case Protections::RuleEdge::WARNING_RAISED: + return "warning_raised"; + case Protections::RuleEdge::RECOVERED: + return "recovered"; + case Protections::RuleEdge::NONE: + return "none"; + } + std::unreachable(); +} + +void format_numeric_value( + Protections::SampleEncoding encoding, + Protections::NumericValue value, + char* buffer, + size_t buffer_size +) { + if (buffer_size == 0) { + return; + } + + switch (encoding) { + case Protections::SampleEncoding::BOOL: + snprintf(buffer, buffer_size, "%s", value.bool_value ? "true" : "false"); + return; + case Protections::SampleEncoding::SIGNED: + snprintf(buffer, buffer_size, "%lld", static_cast(value.signed_value)); + return; + case Protections::SampleEncoding::UNSIGNED: + snprintf( + buffer, + buffer_size, + "%llu", + static_cast(value.unsigned_value) + ); + return; + case Protections::SampleEncoding::FLOAT32: + snprintf(buffer, buffer_size, "%.6f", static_cast(value.float32_value)); + return; + case Protections::SampleEncoding::FLOAT64: + snprintf(buffer, buffer_size, "%.6f", value.float64_value); + return; + } + + std::unreachable(); +} + +void append_timestamp_suffix(const Timestamp& timestamp, char* buffer, size_t buffer_size) { + if (timestamp.has_rtc) { + append_formatted( + buffer, + buffer_size, + " | Timestamp: %04u-%02u-%02u %02u:%02u:%02u.%05u", + static_cast(timestamp.year), + static_cast(timestamp.month), + static_cast(timestamp.day), + static_cast(timestamp.hour), + static_cast(timestamp.minute), + static_cast(timestamp.second), + static_cast(timestamp.counter) + ); + return; + } + +#ifdef HAL_TIM_MODULE_ENABLED + const uint64_t total_seconds = timestamp.uptime_us / 1'000'000ULL; + const uint64_t days = total_seconds / 86'400ULL; + const unsigned hours = static_cast((total_seconds / 3'600ULL) % 24ULL); + const unsigned minutes = static_cast((total_seconds / 60ULL) % 60ULL); + const unsigned seconds = static_cast(total_seconds % 60ULL); + const unsigned micros = static_cast(timestamp.uptime_us % 1'000'000ULL); + + if (days > 0) { + append_formatted( + buffer, + buffer_size, + " | Uptime: %llud %02u:%02u:%02u.%06u", + static_cast(days), + hours, + minutes, + seconds, + micros + ); + } else { + append_formatted( + buffer, + buffer_size, + " | Uptime: %02u:%02u:%02u.%06u", + hours, + minutes, + seconds, + micros + ); + } +#else + (void)timestamp; + (void)buffer; + (void)buffer_size; +#endif +} + +void format_runtime_record( + const DiagnosticRecord& record, + char* buffer, + size_t buffer_size +) { + const RuntimeDiagnosticPayload& runtime = record.payload.runtime; + append_formatted( + buffer, + buffer_size, + "%s | Line: %lu Function: '%s' File: %s", + runtime.message, + static_cast(runtime.line), + runtime.function_name, + runtime.file_name + ); + append_timestamp_suffix(record.timestamp, buffer, buffer_size); + if (runtime.truncated) { + append_formatted(buffer, buffer_size, " | Message truncated"); + } +} + +void format_protection_record( + const DiagnosticRecord& record, + char* buffer, + size_t buffer_size +) { + const ProtectionDiagnosticPayload& protection = record.payload.protection; + char value_buffer[32]{}; + char threshold_a_buffer[32]{}; + char threshold_b_buffer[32]{}; + + format_numeric_value( + protection.sample_encoding, + protection.observed_value, + value_buffer, + sizeof(value_buffer) + ); + format_numeric_value( + protection.sample_encoding, + protection.threshold_a, + threshold_a_buffer, + sizeof(threshold_a_buffer) + ); + if (protection.has_threshold_b) { + format_numeric_value( + protection.sample_encoding, + protection.threshold_b, + threshold_b_buffer, + sizeof(threshold_b_buffer) + ); + } + + append_formatted( + buffer, + buffer_size, + "Protection %s [%s] %s | Rule: %s | Value: %s | ThresholdA: %s", + rule_state_label(protection.state), + rule_edge_label(protection.edge), + record.origin, + rule_kind_label(protection.rule_kind), + value_buffer, + threshold_a_buffer + ); + if (protection.has_threshold_b) { + append_formatted(buffer, buffer_size, " | ThresholdB: %s", threshold_b_buffer); + } + if (protection.rule_kind == Protections::RuleKind::TIME_ACCUMULATION) { + append_formatted( + buffer, + buffer_size, + " | Window: %.3fs | Rate: %.3fHz", + static_cast(protection.time_window_s), + static_cast(protection.sample_rate_hz) + ); + } + append_timestamp_suffix(record.timestamp, buffer, buffer_size); +} + +} // namespace + +void DiagnosticFormatter::describe( + const DiagnosticRecord& record, + char* buffer, + size_t buffer_size +) { + if (buffer_size == 0) { + return; + } + + buffer[0] = '\0'; + switch (record.category) { + case Category::RUNTIME_ERROR: + case Category::RUNTIME_WARNING: + format_runtime_record(record, buffer, buffer_size); + return; + case Category::PROTECTION_EVENT: + format_protection_record(record, buffer, buffer_size); + return; + } + + std::unreachable(); +} + +} // namespace Diagnostics diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp new file mode 100644 index 000000000..3550a526f --- /dev/null +++ b/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp @@ -0,0 +1,216 @@ +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" + +#if defined(HAL_UART_MODULE_ENABLED) && !defined(SIM_ON) +#include "HALAL/Services/Communication/UART/UART.hpp" +#endif + +#ifdef STLIB_ETH +#include "HALAL/Models/Packets/Order.hpp" +#endif + +namespace Diagnostics { + +namespace { + +constexpr uint16_t DIAGNOSTIC_TCP_ORDER_ID = 3555; +constexpr size_t transport_buffer_capacity = 2 + 1 + 1 + 2 + 1 + 1 + 1 + 1 + 1 + 2 + 8 + + Config::origin_capacity + 1 + + Config::formatted_message_capacity + 1; + +size_t bounded_strnlen(const char* src, size_t max_length) { + if (src == nullptr) { + return 0; + } + + size_t length = 0; + while (length < max_length && src[length] != '\0') { + length++; + } + return length; +} + +template +void copy_c_string(char (&dst)[Capacity], const char* src) { + if (Capacity == 0) { + return; + } + + if (src == nullptr) { + dst[0] = '\0'; + return; + } + + const size_t length = bounded_strnlen(src, Capacity - 1); + memcpy(dst, src, length); + dst[length] = '\0'; +} + +constexpr uint16_t to_network_u16(uint16_t value) { + if constexpr (std::endian::native == std::endian::little) { + return std::byteswap(value); + } + return value; +} + +constexpr uint64_t to_network_u64(uint64_t value) { + if constexpr (std::endian::native == std::endian::little) { + return std::byteswap(value); + } + return value; +} + +#if defined(HAL_UART_MODULE_ENABLED) && !defined(SIM_ON) +class UartDiagnosticSink final : public DiagnosticSink { +public: + bool publish(const DiagnosticRecord& record) override { + if (!UART::printf_ready) { + return false; + } + + char description[Config::formatted_message_capacity + 1]{}; + DiagnosticFormatter::describe(record, description, sizeof(description)); + printf("%u: %s%s", std::to_underlying(record.severity), description, endl); + return true; + } +}; +#endif + +#ifdef STLIB_ETH +class DiagnosticTransportOrder final : public Order { +public: + DiagnosticTransportOrder() : id(DIAGNOSTIC_TCP_ORDER_ID) { + Packet::packets[id] = this; + orders[id] = this; + } + + void set_record(const DiagnosticRecord& record, const char* description) { + severity = std::to_underlying(record.severity); + category = std::to_underlying(record.category); + copy_c_string(origin, record.origin); + copy_c_string(message, description); + counter = record.timestamp.counter; + second = record.timestamp.second; + minute = record.timestamp.minute; + hour = record.timestamp.hour; + day = record.timestamp.day; + month = record.timestamp.month; + year = record.timestamp.year; + uptime_us = record.timestamp.uptime_us; + } + + void set_callback(void (*callback)(void)) override { this->callback = callback; } + + void process() override { + if (callback != nullptr) { + callback(); + } + } + + void parse(OrderProtocol* socket, uint8_t* data) override { + (void)socket; + (void)data; + } + + uint8_t* build() override { + auto bytes = span(buffer.data(), buffer.size()); + size_t offset = 0; + + write(bytes, offset, to_network_u16(id)); + write(bytes, offset, severity); + write(bytes, offset, category); + write_string(bytes, offset, origin); + write_string(bytes, offset, message); + write(bytes, offset, to_network_u16(counter)); + write(bytes, offset, second); + write(bytes, offset, minute); + write(bytes, offset, hour); + write(bytes, offset, day); + write(bytes, offset, month); + write(bytes, offset, to_network_u16(year)); + write(bytes, offset, to_network_u64(uptime_us)); + + size = offset; + return reinterpret_cast(buffer.data()); + } + + size_t get_size() override { return size; } + + uint16_t get_id() override { return id; } + + void set_pointer(size_t index, void* pointer) override { + (void)index; + (void)pointer; + } + +private: + template static void write(span bytes, size_t& offset, Value value) { + memcpy(bytes.data() + offset, &value, sizeof(Value)); + offset += sizeof(Value); + } + + static void write_string(span bytes, size_t& offset, const char* text) { + const size_t length = bounded_strnlen(text, bytes.size() - offset - 1); + memcpy(bytes.data() + offset, text, length); + offset += length; + bytes[offset++] = byte{0}; + } + + uint16_t id; + void (*callback)(void) = nullptr; + uint8_t severity{0}; + uint8_t category{0}; + char origin[Config::origin_capacity + 1]{}; + char message[Config::formatted_message_capacity + 1]{}; + uint16_t counter{0}; + uint8_t second{0}; + uint8_t minute{0}; + uint8_t hour{0}; + uint8_t day{0}; + uint8_t month{0}; + uint16_t year{0}; + uint64_t uptime_us{0}; + size_t size{0}; + array buffer{}; +}; + +class OrderProtocolDiagnosticSink final : public DiagnosticSink { +public: + bool publish(const DiagnosticRecord& record) override { + char description[Config::formatted_message_capacity + 1]{}; + DiagnosticFormatter::describe(record, description, sizeof(description)); + transport_order.set_record(record, description); + + bool delivered = false; + for (OrderProtocol* socket : OrderProtocol::sockets) { + if (socket == nullptr) { + continue; + } + delivered = socket->send_order(transport_order) || delivered; + } + return delivered; + } + +private: + DiagnosticTransportOrder transport_order{}; +}; +#endif + +} // namespace + +void Runtime::install_default_sinks() { + if (defaults_installed) { + return; + } + +#if defined(HAL_UART_MODULE_ENABLED) && !defined(SIM_ON) + (void)Hub::emplace_sink(); +#endif + +#ifdef STLIB_ETH + (void)Hub::emplace_sink(); +#endif + + defaults_installed = true; +} + +} // namespace Diagnostics diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp new file mode 100644 index 000000000..7e90ece81 --- /dev/null +++ b/Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp @@ -0,0 +1,36 @@ +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" + +#include "HALAL/Services/Time/Scheduler.hpp" + +#if defined(HAL_RTC_MODULE_ENABLED) && !defined(SIM_ON) +#include "HALAL/Services/Time/RTC.hpp" +#endif + +namespace Diagnostics { + +Timestamp DiagnosticTimestampProvider::capture() { + Timestamp timestamp{}; + +#if defined(HAL_RTC_MODULE_ENABLED) && !defined(SIM_ON) + if (Global_RTC::ensure_started() && Global_RTC::has_valid_time()) { + Global_RTC::update_rtc_data(); + const RTCData& rtc = Global_RTC::global_RTC; + timestamp.has_rtc = true; + timestamp.counter = rtc.counter; + timestamp.second = rtc.second; + timestamp.minute = rtc.minute; + timestamp.hour = rtc.hour; + timestamp.day = rtc.day; + timestamp.month = rtc.month; + timestamp.year = rtc.year; + return timestamp; + } +#endif + +#ifdef HAL_TIM_MODULE_ENABLED + timestamp.uptime_us = Scheduler::get_global_tick(); +#endif + return timestamp; +} + +} // namespace Diagnostics diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp new file mode 100644 index 000000000..997745e95 --- /dev/null +++ b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp @@ -0,0 +1,214 @@ +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" + +namespace Diagnostics { + +array Hub::sink_storage = {}; +array Hub::sinks = {}; +size_t Hub::sink_count = 0; +array Hub::history = {}; +size_t Hub::history_count = 0; +size_t Hub::history_next_index = 0; +array Hub::pending_records = {}; +size_t Hub::pending_count = 0; +bool Runtime::defaults_installed = false; + +namespace { + +constexpr const char* runtime_error_origin = "error_handler"; +constexpr const char* runtime_warning_origin = "info_warning"; + +size_t bounded_strnlen(const char* src, size_t max_length) { + if (src == nullptr) { + return 0; + } + + size_t length = 0; + while (length < max_length && src[length] != '\0') { + length++; + } + return length; +} + +template +void copy_c_string(char (&dst)[Capacity], const char* src, bool* truncated = nullptr) { + if (Capacity == 0) { + if (truncated != nullptr) { + *truncated = true; + } + return; + } + + if (src == nullptr) { + dst[0] = '\0'; + return; + } + + const size_t length = bounded_strnlen(src, Capacity - 1); + memcpy(dst, src, length); + dst[length] = '\0'; + if (truncated != nullptr) { + *truncated = *truncated || src[length] != '\0'; + } +} + +} // namespace + +void Hub::push_history(const DiagnosticRecord& record) { + history[history_next_index] = record; + history_next_index = (history_next_index + 1) % Config::history_capacity; + if (history_count < Config::history_capacity) { + history_count++; + } +} + +void Hub::push_pending(const DiagnosticRecord& record) { + if (pending_count == Config::pending_capacity) { + remove_pending(0); + } + + pending_records[pending_count].record = record; + pending_records[pending_count].delivered_mask = 0; + pending_count++; +} + +void Hub::remove_pending(size_t index) { + if (index >= pending_count) { + return; + } + + for (size_t next = index + 1; next < pending_count; ++next) { + pending_records[next - 1] = pending_records[next]; + } + pending_count--; +} + +void Hub::publish(DiagnosticRecord record) { + push_history(record); + + if (sink_count == 0) { + return; + } + + push_pending(record); +} + +void Hub::publish_runtime_error( + const char* message, + bool truncated, + int line, + const char* func, + const char* file +) { + DiagnosticRecord record{}; + record.severity = Severity::FAULT; + record.category = Category::RUNTIME_ERROR; + record.timestamp = DiagnosticTimestampProvider::capture(); + copy_c_string(record.origin, runtime_error_origin); + record.payload.runtime.line = static_cast(line < 0 ? 0 : line); + record.payload.runtime.truncated = truncated; + copy_c_string(record.payload.runtime.message, message, &record.payload.runtime.truncated); + copy_c_string(record.payload.runtime.function_name, func); + copy_c_string(record.payload.runtime.file_name, file); + publish(record); +} + +void Hub::publish_runtime_warning( + const char* message, + bool truncated, + int line, + const char* func, + const char* file +) { + DiagnosticRecord record{}; + record.severity = Severity::WARNING; + record.category = Category::RUNTIME_WARNING; + record.timestamp = DiagnosticTimestampProvider::capture(); + copy_c_string(record.origin, runtime_warning_origin); + record.payload.runtime.line = static_cast(line < 0 ? 0 : line); + record.payload.runtime.truncated = truncated; + copy_c_string(record.payload.runtime.message, message, &record.payload.runtime.truncated); + copy_c_string(record.payload.runtime.function_name, func); + copy_c_string(record.payload.runtime.file_name, file); + publish(record); +} + +void Hub::publish_protection_event( + const char* protection_name, + Protections::RuleState state, + Protections::RuleEdge edge, + const Protections::RuleSnapshot& snapshot +) { + DiagnosticRecord record{}; + switch (state) { + case Protections::RuleState::FAULT: + record.severity = Severity::FAULT; + break; + case Protections::RuleState::WARNING: + record.severity = Severity::WARNING; + break; + case Protections::RuleState::NORMAL: + record.severity = Severity::INFO; + break; + } + + record.category = Category::PROTECTION_EVENT; + record.timestamp = DiagnosticTimestampProvider::capture(); + copy_c_string(record.origin, protection_name); + record.payload.protection.rule_kind = snapshot.kind; + record.payload.protection.state = state; + record.payload.protection.edge = edge; + record.payload.protection.sample_encoding = snapshot.sample_encoding; + record.payload.protection.observed_value = snapshot.observed_value; + record.payload.protection.threshold_a = snapshot.threshold_a; + record.payload.protection.threshold_b = snapshot.threshold_b; + record.payload.protection.has_threshold_b = snapshot.has_threshold_b; + record.payload.protection.uses_warning_threshold = snapshot.uses_warning_threshold; + record.payload.protection.time_window_s = snapshot.time_window_s; + record.payload.protection.sample_rate_hz = snapshot.sample_rate_hz; + publish(record); +} + +void Hub::flush() { + const uint8_t target_mask = + sink_count == 0 ? 0 : static_cast((1u << sink_count) - 1u); + + for (size_t record_index = 0; record_index < pending_count;) { + PendingRecord& pending_record = pending_records[record_index]; + + for (size_t sink_index = 0; sink_index < sink_count; ++sink_index) { + const uint8_t sink_mask = static_cast(1u << sink_index); + if ((pending_record.delivered_mask & sink_mask) != 0u) { + continue; + } + if (sinks[sink_index] != nullptr && sinks[sink_index]->publish(pending_record.record)) { + pending_record.delivered_mask |= sink_mask; + } + } + + if (pending_record.delivered_mask == target_mask) { + remove_pending(record_index); + } else { + record_index++; + } + } +} + +void Hub::clear_for_testing() { + for (size_t sink_index = 0; sink_index < sink_count; ++sink_index) { + sink_storage[sink_index].reset(); + sinks[sink_index] = nullptr; + } + sink_count = 0; + history_count = 0; + history_next_index = 0; + pending_count = 0; + Runtime::reset_for_testing(); +} + +size_t Hub::history_size_for_testing() { return history_count; } + +size_t Hub::pending_size_for_testing() { return pending_count; } + +void Runtime::reset_for_testing() { defaults_installed = false; } + +} // namespace Diagnostics diff --git a/Src/HALAL/Services/InfoWarning/InfoWarning.cpp b/Src/HALAL/Services/InfoWarning/InfoWarning.cpp index ad7da2caf..5cf8a7a68 100644 --- a/Src/HALAL/Services/InfoWarning/InfoWarning.cpp +++ b/Src/HALAL/Services/InfoWarning/InfoWarning.cpp @@ -6,298 +6,33 @@ */ #include "HALAL/Services/InfoWarning/InfoWarning.hpp" -#include "HALAL/Services/Communication/UART/UART.hpp" -#include "HALAL/Services/Time/RTC.hpp" -#include "HALAL/Services/Time/Scheduler.hpp" -#ifdef STLIB_ETH -#include "HALAL/Models/Packets/Order.hpp" -#endif -namespace { +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" -#ifdef STLIB_ETH -constexpr uint16_t INFO_WARNING_TCP_ORDER_ID = 2555; -constexpr uint8_t INFO_WARNING_BOUNDARY_TYPE_ID = 5; -#endif - -bool warning_sent_via_tcp = false; -bool warning_sent_via_uart = false; -bool tcp_delivery_required = false; -bool uart_delivery_required = false; - -#ifdef STLIB_ETH -uint8_t warning_padding = 0; -uint8_t warning_boundary_type = INFO_WARNING_BOUNDARY_TYPE_ID; -string warning_name = "info_warning"; -string warning_message = "Warning-No-Description-Found"; -uint16_t warning_counter = 0; -uint8_t warning_second = 0; -uint8_t warning_minute = 0; -uint8_t warning_hour = 0; -uint8_t warning_day = 0; -uint8_t warning_month = 0; -uint16_t warning_year = 0; - -class InfoWarningOrder final : public Order { -public: - void set_callback(void (*)(void)) override {} - - void process() override {} - - void parse(OrderProtocol* socket, uint8_t* data) override { - (void)socket; - (void)data; - } - - uint8_t* build() override { - const size_t total_size = get_size(); - if (buffer.size() != total_size) { - buffer.resize(total_size); - } - - uint8_t* data = buffer.data(); - append(data, &id, sizeof(id)); - append(data, &warning_padding, sizeof(warning_padding)); - append(data, &warning_boundary_type, sizeof(warning_boundary_type)); - append(data, warning_name.c_str(), warning_name.size() + 1); - append(data, warning_message.c_str(), warning_message.size() + 1); - append(data, &warning_counter, sizeof(warning_counter)); - append(data, &warning_second, sizeof(warning_second)); - append(data, &warning_minute, sizeof(warning_minute)); - append(data, &warning_hour, sizeof(warning_hour)); - append(data, &warning_day, sizeof(warning_day)); - append(data, &warning_month, sizeof(warning_month)); - append(data, &warning_year, sizeof(warning_year)); - return buffer.data(); - } - - size_t get_size() override { - size = sizeof(id) + sizeof(warning_padding) + sizeof(warning_boundary_type) + - warning_name.size() + 1 + warning_message.size() + 1 + sizeof(warning_counter) + - sizeof(warning_second) + sizeof(warning_minute) + sizeof(warning_hour) + - sizeof(warning_day) + sizeof(warning_month) + sizeof(warning_year); - return size; - } - - uint16_t get_id() override { return id; } - - void set_pointer(size_t index, void* pointer) override { - (void)index; - (void)pointer; - } - -private: - static void append(uint8_t*& dst, const void* src, size_t count) { - memcpy(dst, src, count); - dst += count; - } - - static constexpr uint16_t id = INFO_WARNING_TCP_ORDER_ID; - vector buffer{}; -}; - -InfoWarningOrder info_warning_order; - -void refresh_warning_transport_state(const string& description) { - warning_message = description; - -#ifdef HAL_RTC_MODULE_ENABLED - if (Global_RTC::ensure_started()) { - Global_RTC::update_rtc_data(); - warning_counter = Global_RTC::global_RTC.counter; - warning_second = Global_RTC::global_RTC.second; - warning_minute = Global_RTC::global_RTC.minute; - warning_hour = Global_RTC::global_RTC.hour; - warning_day = Global_RTC::global_RTC.day; - warning_month = Global_RTC::global_RTC.month; - warning_year = Global_RTC::global_RTC.year; - return; - } -#endif - - warning_counter = 0; - warning_second = 0; - warning_minute = 0; - warning_hour = 0; - warning_day = 0; - warning_month = 0; - warning_year = 0; -} -#endif - -bool try_send_warning_via_tcp(const string& description) { -#ifdef STLIB_ETH - if (!tcp_delivery_required || warning_sent_via_tcp) { - return true; - } - - refresh_warning_transport_state(description); - - bool delivered = false; - for (OrderProtocol* socket : OrderProtocol::sockets) { - if (socket == nullptr) { - continue; - } - delivered = socket->send_order(info_warning_order) || delivered; - } - - if (delivered) { - warning_sent_via_tcp = true; - } - return warning_sent_via_tcp; -#else - (void)description; - return true; -#endif -} - -bool try_send_warning_via_uart(const string& description) { -#ifdef HAL_UART_MODULE_ENABLED - if (!uart_delivery_required || warning_sent_via_uart) { - return true; - } - - if (!UART::printf_ready) { - return false; - } - - printf("Warning: %s%s", description.c_str(), endl); - warning_sent_via_uart = true; - return true; -#else - (void)description; - return true; -#endif -} - -void append_readable_timestamp(string& message) { -#ifdef HAL_RTC_MODULE_ENABLED - if (Global_RTC::ensure_started() && Global_RTC::has_valid_time()) { - Global_RTC::update_rtc_data(); - const RTCData& timestamp = Global_RTC::global_RTC; - char buffer[80]{}; - snprintf( - buffer, - sizeof(buffer), - " | Timestamp: %04u-%02u-%02u %02u:%02u:%02u.%05u", - static_cast(timestamp.year), - static_cast(timestamp.month), - static_cast(timestamp.day), - static_cast(timestamp.hour), - static_cast(timestamp.minute), - static_cast(timestamp.second), - static_cast(timestamp.counter) - ); - message += buffer; - return; - } -#endif - -#ifdef HAL_TIM_MODULE_ENABLED - const uint64_t uptime_us = Scheduler::get_global_tick(); - const uint64_t total_seconds = uptime_us / 1'000'000ULL; - const uint64_t days = total_seconds / 86'400ULL; - const unsigned hours = static_cast((total_seconds / 3'600ULL) % 24ULL); - const unsigned minutes = static_cast((total_seconds / 60ULL) % 60ULL); - const unsigned seconds = static_cast(total_seconds % 60ULL); - const unsigned micros = static_cast(uptime_us % 1'000'000ULL); - - char buffer[80]{}; - if (days > 0) { - snprintf( - buffer, - sizeof(buffer), - " | Uptime: %llud %02u:%02u:%02u.%06u", - static_cast(days), - hours, - minutes, - seconds, - micros - ); - } else { - snprintf( - buffer, - sizeof(buffer), - " | Uptime: %02u:%02u:%02u.%06u", - hours, - minutes, - seconds, - micros - ); - } - message += buffer; -#endif -} - -} // namespace - -string InfoWarning::description = "Warning-No-Description-Found"; -string InfoWarning::line = "Warning-No-Line-Found"; -string InfoWarning::func = "Warning-No-Func-Found"; -string InfoWarning::file = "Warning-No-File-Found"; -bool InfoWarning::warning_triggered = false; -bool InfoWarning::warning_to_communicate = false; +int InfoWarning::line = 0; +const char* InfoWarning::func = "Warning-No-Func-Found"; +const char* InfoWarning::file = "Warning-No-File-Found"; void InfoWarning::SetMetaData(int line, const char* func, const char* file) { - InfoWarning::line = to_string(line); - InfoWarning::func = string(func); - InfoWarning::file = string(file); + InfoWarning::line = line; + InfoWarning::func = func; + InfoWarning::file = file; } -void InfoWarning::InfoWarningTrigger(string format, ...) { - if (InfoWarning::warning_triggered) { - return; - } - - InfoWarning::warning_triggered = true; - InfoWarning::warning_to_communicate = true; - warning_sent_via_tcp = false; - warning_sent_via_uart = false; -#ifdef STLIB_ETH - tcp_delivery_required = true; -#else - tcp_delivery_required = false; -#endif -#ifdef HAL_UART_MODULE_ENABLED - uart_delivery_required = UART::printf_ready; -#else - uart_delivery_required = false; -#endif - - if (format.length() != 0) { - description = ""; - } - +void InfoWarning::InfoWarningTrigger(const char* format, ...) { + char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; va_list arguments; va_start(arguments, format); - va_list arg_copy; - va_copy(arg_copy, arguments); - - const int32_t size = vsnprintf(nullptr, 0, format.c_str(), arguments) + 1; - const unique_ptr buffer = make_unique(size); + const int32_t written = vsnprintf(buffer, sizeof(buffer), format, arguments); va_end(arguments); - vsnprintf(buffer.get(), size, format.c_str(), arg_copy); - va_end(arg_copy); - - description += string(buffer.get(), buffer.get() + size - 1) + " | Line: " + InfoWarning::line + - " Function: '" + InfoWarning::func + "' File: " + InfoWarning::file; - - append_readable_timestamp(description); - - InfoWarning::InfoWarningUpdate(); + Diagnostics::Hub::publish_runtime_warning( + buffer, + written < 0 || static_cast(written) >= sizeof(buffer), + line, + func, + file + ); } -void InfoWarning::InfoWarningUpdate() { - if (!InfoWarning::warning_to_communicate) { - return; - } - - const bool tcp_done = try_send_warning_via_tcp(InfoWarning::description); - const bool uart_done = try_send_warning_via_uart(InfoWarning::description); - - if (tcp_done && uart_done) { - InfoWarning::warning_to_communicate = false; - InfoWarning::warning_triggered = false; - } -} +void InfoWarning::InfoWarningUpdate() { Diagnostics::Hub::flush(); } diff --git a/Src/ST-LIB.cpp b/Src/ST-LIB.cpp index fb8a4378d..4e1ca7dd4 100644 --- a/Src/ST-LIB.cpp +++ b/Src/ST-LIB.cpp @@ -1,4 +1,6 @@ #include "ST-LIB.hpp" +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" +#include "ST-LIB_HIGH/Protections/FaultRuntime.hpp" #ifdef STLIB_ETH @@ -11,6 +13,8 @@ void STLIB::start( UART::Peripheral& printf_peripheral ) { HALAL::start(mac, ip, subnet_mask, gateway, printf_peripheral); + Diagnostics::Runtime::install_default_sinks(); + FaultRuntime::install_default_broadcasters(); STLIB_LOW::start(); STLIB_HIGH::start(); } @@ -29,6 +33,8 @@ void STLIB::start( void STLIB::start(UART::Peripheral& printf_peripheral) { HALAL::start(printf_peripheral); + Diagnostics::Runtime::install_default_sinks(); + FaultRuntime::install_default_broadcasters(); STLIB_LOW::start(); STLIB_HIGH::start(); } @@ -47,6 +53,6 @@ void STLIB::update() { Ethernet::update(); Server::update_servers(); #endif - ErrorHandlerModel::ErrorHandlerUpdate(); + Diagnostics::Hub::flush(); MDMA::update(); } diff --git a/Src/ST-LIB_HIGH/Protections/FaultController.cpp b/Src/ST-LIB_HIGH/Protections/FaultController.cpp new file mode 100644 index 000000000..572c7ef74 --- /dev/null +++ b/Src/ST-LIB_HIGH/Protections/FaultController.cpp @@ -0,0 +1,55 @@ +#include "ST-LIB_HIGH/Protections/FaultController.hpp" + +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" +#include "ST-LIB_HIGH/Protections/FaultRuntime.hpp" + +IStateMachine* FaultController::general_state_machine = nullptr; +FaultController::state_id FaultController::fault_state_id = 255; +array + FaultController::broadcaster_storage = {}; +array FaultController::broadcasters = {}; +size_t FaultController::broadcaster_count = 0; +bool FaultRuntime::defaults_installed = false; + +void FaultController::link_state_machine( + IStateMachine& general_state_machine, + FaultController::state_id fault_id +) { + FaultController::general_state_machine = &general_state_machine; + FaultController::fault_state_id = fault_id; +} + +void FaultController::enter_fault() { + if (general_state_machine == nullptr) { + Diagnostics::Hub::publish_runtime_error( + "FaultController does not have General State Machine linked", + false, + 0, + "FaultController::enter_fault", + __FILE__ + ); + return; + } + + if (general_state_machine->get_current_state_id() == fault_state_id) { + return; + } + + general_state_machine->force_change_state(fault_state_id); + for (size_t broadcaster_index = 0; broadcaster_index < broadcaster_count; broadcaster_index++) { + if (broadcasters[broadcaster_index] != nullptr) { + broadcasters[broadcaster_index]->broadcast_fault(); + } + } +} + +void FaultController::enter_external_fault() { enter_fault(); } + +void FaultController::clear_broadcasters_for_testing() { + for (size_t broadcaster_index = 0; broadcaster_index < broadcaster_count; broadcaster_index++) { + broadcaster_storage[broadcaster_index].reset(); + broadcasters[broadcaster_index] = nullptr; + } + broadcaster_count = 0; + FaultRuntime::reset_for_testing(); +} diff --git a/Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp b/Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp new file mode 100644 index 000000000..51b178990 --- /dev/null +++ b/Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp @@ -0,0 +1,68 @@ +#include "ST-LIB_HIGH/Protections/FaultRuntime.hpp" + +#include "ST-LIB_HIGH/Protections/FaultController.hpp" + +#include "HALAL/Models/Packets/Order.hpp" +#include "HALAL/Services/Communication/FDCAN/FDCAN.hpp" + +namespace { + +constexpr uint16_t remote_fault_order_id = 0; + +void handle_remote_fault() { FaultController::enter_external_fault(); } + +StackOrder<0>& fault_order() { + static StackOrder<0> order(remote_fault_order_id, handle_remote_fault); + return order; +} + +#if defined(HAL_FDCAN_MODULE_ENABLED) && !defined(SIM_ON) +class FdcanFaultBroadcaster final : public FaultBroadcaster { +public: + bool broadcast_fault() override { + bool delivered = false; + for (const auto& [key, value] : FDCAN::registered_fdcan) { + (void)value; + delivered = FDCAN::transmit(key, FDCAN::ID::FAULT_ID, NULL) || delivered; + } + return delivered; + } +}; +#endif + +#ifdef STLIB_ETH +class OrderProtocolFaultBroadcaster final : public FaultBroadcaster { +public: + bool broadcast_fault() override { + bool delivered = false; + for (OrderProtocol* socket : OrderProtocol::sockets) { + if (socket == nullptr) { + continue; + } + delivered = socket->send_order(fault_order()) || delivered; + } + return delivered; + } +}; +#endif + +} // namespace + +void FaultRuntime::install_default_broadcasters() { + if (defaults_installed) { + return; + } + +#if defined(HAL_FDCAN_MODULE_ENABLED) && !defined(SIM_ON) + (void)FaultController::emplace_broadcaster(); +#endif + +#ifdef STLIB_ETH + (void)fault_order(); + (void)FaultController::emplace_broadcaster(); +#endif + + defaults_installed = true; +} + +void FaultRuntime::reset_for_testing() { defaults_installed = false; } diff --git a/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp b/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp index b0c4897da..0cb0f2d7f 100644 --- a/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp +++ b/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp @@ -6,300 +6,35 @@ */ #include "ErrorHandler/ErrorHandler.hpp" -#include "HALAL/Services/Time/Scheduler.hpp" -#include "HALAL/Services/Time/RTC.hpp" -#ifdef STLIB_ETH -#include "HALAL/Models/Packets/Order.hpp" -#endif -namespace { +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" +#include "ST-LIB_HIGH/Protections/FaultController.hpp" -#ifdef STLIB_ETH -constexpr uint16_t ERROR_HANDLER_TCP_ORDER_ID = 1555; -constexpr uint8_t ERROR_HANDLER_BOUNDARY_TYPE_ID = 5; -#endif - -bool error_sent_via_tcp = false; -bool error_sent_via_uart = false; -bool tcp_delivery_required = false; -bool uart_delivery_required = false; - -#ifdef STLIB_ETH -uint8_t error_handler_padding = 0; -uint8_t error_handler_boundary_type = ERROR_HANDLER_BOUNDARY_TYPE_ID; -string error_handler_name = "error_handler"; -string error_handler_message = "Error-No-Description-Found"; -uint16_t error_handler_counter = 0; -uint8_t error_handler_second = 0; -uint8_t error_handler_minute = 0; -uint8_t error_handler_hour = 0; -uint8_t error_handler_day = 0; -uint8_t error_handler_month = 0; -uint16_t error_handler_year = 0; - -class ErrorHandlerOrder final : public Order { -public: - void set_callback(void (*)(void)) override {} - - void process() override {} - - void parse(OrderProtocol* socket, uint8_t* data) override { - (void)socket; - (void)data; - } - - uint8_t* build() override { - const size_t total_size = get_size(); - if (buffer.size() != total_size) { - buffer.resize(total_size); - } - - uint8_t* data = buffer.data(); - append(data, &id, sizeof(id)); - append(data, &error_handler_padding, sizeof(error_handler_padding)); - append(data, &error_handler_boundary_type, sizeof(error_handler_boundary_type)); - append(data, error_handler_name.c_str(), error_handler_name.size() + 1); - append(data, error_handler_message.c_str(), error_handler_message.size() + 1); - append(data, &error_handler_counter, sizeof(error_handler_counter)); - append(data, &error_handler_second, sizeof(error_handler_second)); - append(data, &error_handler_minute, sizeof(error_handler_minute)); - append(data, &error_handler_hour, sizeof(error_handler_hour)); - append(data, &error_handler_day, sizeof(error_handler_day)); - append(data, &error_handler_month, sizeof(error_handler_month)); - append(data, &error_handler_year, sizeof(error_handler_year)); - return buffer.data(); - } - - size_t get_size() override { - size = sizeof(id) + sizeof(error_handler_padding) + sizeof(error_handler_boundary_type) + - error_handler_name.size() + 1 + error_handler_message.size() + 1 + - sizeof(error_handler_counter) + sizeof(error_handler_second) + - sizeof(error_handler_minute) + sizeof(error_handler_hour) + - sizeof(error_handler_day) + sizeof(error_handler_month) + sizeof(error_handler_year); - return size; - } - - uint16_t get_id() override { return id; } - - void set_pointer(size_t index, void* pointer) override { - (void)index; - (void)pointer; - } - -private: - static void append(uint8_t*& dst, const void* src, size_t count) { - memcpy(dst, src, count); - dst += count; - } - - static constexpr uint16_t id = ERROR_HANDLER_TCP_ORDER_ID; - vector buffer{}; -}; - -ErrorHandlerOrder error_handler_order; - -void refresh_error_handler_transport_state(const string& description) { - error_handler_message = description; - -#ifdef HAL_RTC_MODULE_ENABLED - if (Global_RTC::ensure_started()) { - Global_RTC::update_rtc_data(); - error_handler_counter = Global_RTC::global_RTC.counter; - error_handler_second = Global_RTC::global_RTC.second; - error_handler_minute = Global_RTC::global_RTC.minute; - error_handler_hour = Global_RTC::global_RTC.hour; - error_handler_day = Global_RTC::global_RTC.day; - error_handler_month = Global_RTC::global_RTC.month; - error_handler_year = Global_RTC::global_RTC.year; - return; - } -#endif - - error_handler_counter = 0; - error_handler_second = 0; - error_handler_minute = 0; - error_handler_hour = 0; - error_handler_day = 0; - error_handler_month = 0; - error_handler_year = 0; -} -#endif - -bool try_send_error_via_tcp(const string& description) { -#ifdef STLIB_ETH - if (!tcp_delivery_required || error_sent_via_tcp) { - return true; - } - - refresh_error_handler_transport_state(description); - - bool delivered = false; - for (OrderProtocol* socket : OrderProtocol::sockets) { - if (socket == nullptr) { - continue; - } - delivered = socket->send_order(error_handler_order) || delivered; - } - - if (delivered) { - error_sent_via_tcp = true; - } - return error_sent_via_tcp; -#else - (void)description; - return true; -#endif -} - -bool try_send_error_via_uart(const string& description) { -#ifdef HAL_UART_MODULE_ENABLED - if (!uart_delivery_required || error_sent_via_uart) { - return true; - } - - if (!UART::printf_ready) { - return false; - } - - printf("Error: %s%s", description.c_str(), endl); - error_sent_via_uart = true; - return true; -#else - (void)description; - return true; -#endif -} - -void append_readable_timestamp(string& message) { -#ifdef HAL_RTC_MODULE_ENABLED - if (Global_RTC::ensure_started() && Global_RTC::has_valid_time()) { - Global_RTC::update_rtc_data(); - const RTCData& timestamp = Global_RTC::global_RTC; - char buffer[80]{}; - snprintf( - buffer, - sizeof(buffer), - " | Timestamp: %04u-%02u-%02u %02u:%02u:%02u.%05u", - static_cast(timestamp.year), - static_cast(timestamp.month), - static_cast(timestamp.day), - static_cast(timestamp.hour), - static_cast(timestamp.minute), - static_cast(timestamp.second), - static_cast(timestamp.counter) - ); - message += buffer; - return; - } -#endif - -#ifdef HAL_TIM_MODULE_ENABLED - const uint64_t uptime_us = Scheduler::get_global_tick(); - const uint64_t total_seconds = uptime_us / 1'000'000ULL; - const uint64_t days = total_seconds / 86'400ULL; - const unsigned hours = static_cast((total_seconds / 3'600ULL) % 24ULL); - const unsigned minutes = static_cast((total_seconds / 60ULL) % 60ULL); - const unsigned seconds = static_cast(total_seconds % 60ULL); - const unsigned micros = static_cast(uptime_us % 1'000'000ULL); - - char buffer[80]{}; - if (days > 0) { - snprintf( - buffer, - sizeof(buffer), - " | Uptime: %llud %02u:%02u:%02u.%06u", - static_cast(days), - hours, - minutes, - seconds, - micros - ); - } else { - snprintf( - buffer, - sizeof(buffer), - " | Uptime: %02u:%02u:%02u.%06u", - hours, - minutes, - seconds, - micros - ); - } - message += buffer; -#endif -} - -} // namespace - -string ErrorHandlerModel::description = "Error-No-Description-Found"; -string ErrorHandlerModel::line = "Error-No-Line-Found"; -string ErrorHandlerModel::func = "Error-No-Func-Found"; -string ErrorHandlerModel::file = "Error-No-File-Found"; -double ErrorHandlerModel::error_triggered = 0; -bool ErrorHandlerModel::error_to_communicate = false; +int ErrorHandlerModel::line = 0; +const char* ErrorHandlerModel::func = "Error-No-Func-Found"; +const char* ErrorHandlerModel::file = "Error-No-File-Found"; void ErrorHandlerModel::SetMetaData(int line, const char* func, const char* file) { - ErrorHandlerModel::line = to_string(line); - ErrorHandlerModel::func = string(func); - ErrorHandlerModel::file = string(file); + ErrorHandlerModel::line = line; + ErrorHandlerModel::func = func; + ErrorHandlerModel::file = file; } -void ErrorHandlerModel::ErrorHandlerTrigger(string format, ...) { - if (ErrorHandlerModel::error_triggered) { - return; - } - - ErrorHandlerModel::error_triggered = 1.0; - ErrorHandlerModel::error_to_communicate = - true; // This flag is marked so the ProtectionManager can know if it already consumed the - // error in question. - error_sent_via_tcp = false; - error_sent_via_uart = false; -#ifdef STLIB_ETH - tcp_delivery_required = true; -#else - tcp_delivery_required = false; -#endif -#ifdef HAL_UART_MODULE_ENABLED - uart_delivery_required = UART::printf_ready; -#else - uart_delivery_required = false; -#endif - - if (format.length() != 0) { - description = ""; - } - +void ErrorHandlerModel::ErrorHandlerTrigger(const char* format, ...) { + char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; va_list arguments; va_start(arguments, format); - va_list arg_copy; - va_copy(arg_copy, arguments); - - const int32_t size = vsnprintf(nullptr, 0, format.c_str(), arguments) + 1; - const unique_ptr buffer = make_unique(size); + const int32_t written = vsnprintf(buffer, sizeof(buffer), format, arguments); va_end(arguments); - vsnprintf(buffer.get(), size, format.c_str(), arg_copy); - va_end(arg_copy); - - description += string(buffer.get(), buffer.get() + size - 1) + - " | Line: " + ErrorHandlerModel::line + " Function: '" + - ErrorHandlerModel::func + "' File: " + ErrorHandlerModel::file; - - append_readable_timestamp(description); - - ErrorHandlerModel::ErrorHandlerUpdate(); + Diagnostics::Hub::publish_runtime_error( + buffer, + written < 0 || static_cast(written) >= sizeof(buffer), + line, + func, + file + ); + FaultController::enter_fault(); } -void ErrorHandlerModel::ErrorHandlerUpdate() { - if (!ErrorHandlerModel::error_triggered || !ErrorHandlerModel::error_to_communicate) { - return; - } - - const bool tcp_done = try_send_error_via_tcp(ErrorHandlerModel::description); - const bool uart_done = try_send_error_via_uart(ErrorHandlerModel::description); - - if (tcp_done && uart_done) { - ErrorHandlerModel::error_to_communicate = false; - } -} +void ErrorHandlerModel::ErrorHandlerUpdate() { Diagnostics::Hub::flush(); } diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index da790098b..dc105d47a 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -17,15 +17,26 @@ FetchContent_MakeAvailable(googletest) message(STATUS "Generating test executable for ST-LIB") add_executable(${STLIB_TEST_EXECUTABLE} + ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Models/Packets/Packet.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Services/InfoWarning/InfoWarning.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Services/Time/RTC.cpp ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Models/SPI/SPI2.cpp ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Models/DMA/DMA2.cpp ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Services/DFSDM/DFSDM.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Src/ST-LIB_HIGH/Protections/FaultController.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp + ${CMAKE_CURRENT_LIST_DIR}/../Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/encoder_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/scheduler_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/timer_wrapper_test.cpp ${CMAKE_CURRENT_LIST_DIR}/StateMachine/state_machine_test.cpp ${CMAKE_CURRENT_LIST_DIR}/adc_test.cpp ${CMAKE_CURRENT_LIST_DIR}/adc_sensor_test.cpp + ${CMAKE_CURRENT_LIST_DIR}/diagnostics_test.cpp ${CMAKE_CURRENT_LIST_DIR}/spi2_test.cpp ${CMAKE_CURRENT_LIST_DIR}/dma2_test.cpp ${CMAKE_CURRENT_LIST_DIR}/dfsdm_test.cpp diff --git a/Tests/Time/common_tests.cpp b/Tests/Time/common_tests.cpp index eca45e017..6e34db21d 100644 --- a/Tests/Time/common_tests.cpp +++ b/Tests/Time/common_tests.cpp @@ -1,10 +1,12 @@ #include +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" +#include "ST-LIB_HIGH/Protections/FaultController.hpp" #include "ErrorHandler/ErrorHandler.hpp" #include "HALAL/Services/InfoWarning/InfoWarning.hpp" -std::string ErrorHandlerModel::line; -std::string ErrorHandlerModel::func; -std::string ErrorHandlerModel::file; +int ErrorHandlerModel::line = 0; +const char* ErrorHandlerModel::func = ""; +const char* ErrorHandlerModel::file = ""; namespace ST_LIB::TestErrorHandler { bool fail_on_error = true; @@ -19,14 +21,27 @@ void set_fail_on_error(bool enabled) { fail_on_error = enabled; } } // namespace ST_LIB::TestErrorHandler void ErrorHandlerModel::SetMetaData(int line, const char* func, const char* file) { - ErrorHandlerModel::line = to_string(line); - ErrorHandlerModel::func = string(func); - ErrorHandlerModel::file = string(file); + ErrorHandlerModel::line = line; + ErrorHandlerModel::func = func; + ErrorHandlerModel::file = file; } -void ErrorHandlerModel::ErrorHandlerTrigger(string format, ...) { - (void)format; +void ErrorHandlerModel::ErrorHandlerTrigger(const char* format, ...) { + char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; + va_list arguments; + va_start(arguments, format); + const int32_t written = vsnprintf(buffer, sizeof(buffer), format, arguments); + va_end(arguments); + ST_LIB::TestErrorHandler::call_count++; + Diagnostics::Hub::publish_runtime_error( + buffer, + written < 0 || static_cast(written) >= sizeof(buffer), + line, + func, + file + ); + FaultController::enter_fault(); if (ST_LIB::TestErrorHandler::fail_on_error) { EXPECT_EQ(1, 0); } diff --git a/Tests/diagnostics_test.cpp b/Tests/diagnostics_test.cpp new file mode 100644 index 000000000..272ab7a10 --- /dev/null +++ b/Tests/diagnostics_test.cpp @@ -0,0 +1,228 @@ +#include + +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" +#include "HALAL/Services/InfoWarning/InfoWarning.hpp" +#include "ST-LIB_HIGH/Protections/FaultController.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionEngine.hpp" +#include "ST-LIB_HIGH/Protections/Rules.hpp" +#include "ST-LIB_HIGH/Protections/SampleSource.hpp" +#include "ErrorHandler/ErrorHandler.hpp" + +namespace ST_LIB::TestErrorHandler { +void set_fail_on_error(bool enabled); +} + +namespace { + +namespace TestErrorHandler = ST_LIB::TestErrorHandler; + +static_assert(Protections::ReadableSampleSource>); +static_assert(!Protections::ReadableSampleSource); +static_assert(Protections::FloatingSample); +static_assert(!Protections::FloatingSample); +static_assert(Protections::ComparableSample); +static_assert(!Protections::ComparableSample); + +class RecordingSink final : public Diagnostics::DiagnosticSink { +public: + explicit RecordingSink(size_t failures_before_success = 0) + : failures_before_success(failures_before_success) {} + + bool publish(const Diagnostics::DiagnosticRecord& record) override { + publish_calls++; + records.push_back(record); + if (publish_calls <= failures_before_success) { + return false; + } + return true; + } + + size_t failures_before_success; + size_t publish_calls{0}; + vector records{}; +}; + +class DummyStateMachine final : public IStateMachine { +public: + void check_transitions() override {} + + void force_change_state(size_t state) override { + force_change_calls++; + current_state_id = state; + } + + size_t get_current_state_id() const override { return current_state_id; } + + size_t force_change_calls{0}; + size_t current_state_id{0}; + +protected: + void enter() override {} + void exit() override {} + void start() override {} +}; + +class CountingBroadcaster final : public FaultBroadcaster { +public: + bool broadcast_fault() override { + calls++; + return true; + } + + size_t calls{0}; +}; + +class DiagnosticsHubTest : public ::testing::Test { +protected: + void SetUp() override { + Diagnostics::Hub::clear_for_testing(); + ProtectionEngine::clear_for_testing(); + FaultController::clear_broadcasters_for_testing(); + } +}; + +TEST_F(DiagnosticsHubTest, KeepsLocalHistoryWhenNoSinksAreRegistered) { + Diagnostics::DiagnosticRecord record{}; + record.severity = Diagnostics::Severity::WARNING; + record.category = Diagnostics::Category::RUNTIME_WARNING; + snprintf(record.origin, sizeof(record.origin), "test"); + snprintf(record.payload.runtime.message, sizeof(record.payload.runtime.message), "local only"); + Diagnostics::Hub::publish(record); + + EXPECT_EQ(Diagnostics::Hub::history_size_for_testing(), 1u); + EXPECT_EQ(Diagnostics::Hub::pending_size_for_testing(), 0u); +} + +TEST_F(DiagnosticsHubTest, RetriesOnlyTheSinkThatFailed) { + auto stable_sink_result = Diagnostics::Hub::emplace_sink(); + auto flaky_sink_result = Diagnostics::Hub::emplace_sink(1); + ASSERT_TRUE(stable_sink_result.has_value()); + ASSERT_TRUE(flaky_sink_result.has_value()); + auto* stable_sink = *stable_sink_result; + auto* flaky_sink = *flaky_sink_result; + + Diagnostics::DiagnosticRecord record{}; + record.severity = Diagnostics::Severity::WARNING; + record.category = Diagnostics::Category::RUNTIME_WARNING; + snprintf(record.origin, sizeof(record.origin), "test"); + snprintf(record.payload.runtime.message, sizeof(record.payload.runtime.message), "retry me"); + Diagnostics::Hub::publish(record); + + Diagnostics::Hub::flush(); + EXPECT_EQ(stable_sink->publish_calls, 1u); + EXPECT_EQ(flaky_sink->publish_calls, 1u); + EXPECT_EQ(Diagnostics::Hub::pending_size_for_testing(), 1u); + + Diagnostics::Hub::flush(); + EXPECT_EQ(stable_sink->publish_calls, 1u); + EXPECT_EQ(flaky_sink->publish_calls, 2u); + EXPECT_EQ(Diagnostics::Hub::pending_size_for_testing(), 0u); +} + +TEST_F(DiagnosticsHubTest, PendingQueueIsBoundedWhenASinkNeverDelivers) { + auto stuck_sink_result = + Diagnostics::Hub::emplace_sink(std::numeric_limits::max()); + ASSERT_TRUE(stuck_sink_result.has_value()); + + for (size_t record_index = 0; record_index < Diagnostics::Config::pending_capacity + 5; + record_index++) { + Diagnostics::DiagnosticRecord record{}; + record.severity = Diagnostics::Severity::WARNING; + record.category = Diagnostics::Category::RUNTIME_WARNING; + snprintf(record.origin, sizeof(record.origin), "test"); + snprintf(record.payload.runtime.message, sizeof(record.payload.runtime.message), "event %zu", record_index); + Diagnostics::Hub::publish(record); + } + + EXPECT_EQ(Diagnostics::Hub::pending_size_for_testing(), Diagnostics::Config::pending_capacity); +} + +TEST_F(DiagnosticsHubTest, FaultControllerTransitionsOnlyOnce) { + DummyStateMachine machine{}; + + FaultController::link_state_machine(machine, 4); + auto broadcaster_result = FaultController::emplace_broadcaster(); + ASSERT_TRUE(broadcaster_result.has_value()); + auto* broadcaster = *broadcaster_result; + + FaultController::enter_fault(); + FaultController::enter_fault(); + + EXPECT_EQ(machine.current_state_id, 4u); + EXPECT_EQ(machine.force_change_calls, 1u); + EXPECT_EQ(broadcaster->calls, 1u); +} + +TEST_F(DiagnosticsHubTest, ProtectionEngineEvaluatesRulesAndPublishesSnapshots) { + DummyStateMachine machine{}; + float monitored_value = 2.0f; + SampleSource source(monitored_value); + + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + FaultController::link_state_machine(machine, 7); + auto protection = ProtectionEngine::create_protection("monitored_value", source); + ASSERT_TRUE(protection.has_value()); + ASSERT_TRUE(protection->add_rule(Protections::Rules::below(1.0f, 1.5f)).has_value()); + + ProtectionEngine::initialize(); + monitored_value = 0.5f; + ProtectionEngine::evaluate(); + Diagnostics::Hub::flush(); + + ASSERT_FALSE(sink->records.empty()); + EXPECT_EQ(machine.current_state_id, 7u); + EXPECT_EQ(sink->records.front().category, Diagnostics::Category::PROTECTION_EVENT); + EXPECT_EQ( + sink->records.front().payload.protection.state, + Protections::RuleState::FAULT + ); + EXPECT_EQ( + sink->records.front().payload.protection.rule_kind, + Protections::RuleKind::BELOW + ); +} + +TEST_F(DiagnosticsHubTest, ErrorHandlerPublishesAndEntersFault) { + DummyStateMachine machine{}; + + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + FaultController::link_state_machine(machine, 5); + TestErrorHandler::set_fail_on_error(false); + + ErrorHandler("runtime failure %d", 12); + Diagnostics::Hub::flush(); + + ASSERT_FALSE(sink->records.empty()); + EXPECT_EQ(machine.current_state_id, 5u); + EXPECT_EQ(sink->records.front().category, Diagnostics::Category::RUNTIME_ERROR); + EXPECT_EQ(sink->records.front().severity, Diagnostics::Severity::FAULT); +} + +TEST_F(DiagnosticsHubTest, WarningDoesNotEnterFault) { + DummyStateMachine machine{}; + + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + FaultController::link_state_machine(machine, 9); + + WARNING("runtime warning %d", 3); + Diagnostics::Hub::flush(); + + ASSERT_FALSE(sink->records.empty()); + EXPECT_EQ(machine.current_state_id, 0u); + EXPECT_EQ(sink->records.front().category, Diagnostics::Category::RUNTIME_WARNING); + EXPECT_EQ(sink->records.front().severity, Diagnostics::Severity::WARNING); +} + +TEST_F(DiagnosticsHubTest, RejectsInvalidRuleConfigurationsWithoutGlobalSideEffects) { + auto invalid_rule = Protections::Rules::below(1.0f, 0.5f); + EXPECT_FALSE(invalid_rule.has_value()); + EXPECT_EQ(invalid_rule.error(), Protections::RuleConfigError::INVALID_WARNING_THRESHOLD); +} + +} // namespace From 33b66bee26bca25f00a024dd132aba476057509c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Sun, 5 Apr 2026 11:36:41 +0200 Subject: [PATCH 03/23] Harden simulator and time service support --- Inc/HALAL/Services/Time/RTC.hpp | 4 --- Inc/HALAL/Services/Time/Scheduler.hpp | 16 +++++++---- Src/HALAL/Services/Time/RTC.cpp | 40 ++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/Inc/HALAL/Services/Time/RTC.hpp b/Inc/HALAL/Services/Time/RTC.hpp index baf02c639..9e12e0dc9 100644 --- a/Inc/HALAL/Services/Time/RTC.hpp +++ b/Inc/HALAL/Services/Time/RTC.hpp @@ -5,8 +5,6 @@ #define RTC_MAX_COUNTER 32767 -#ifdef HAL_RTC_MODULE_ENABLED - struct RTCData { uint16_t counter; uint8_t second; @@ -35,5 +33,3 @@ class Global_RTC { uint16_t year ); }; - -#endif diff --git a/Inc/HALAL/Services/Time/Scheduler.hpp b/Inc/HALAL/Services/Time/Scheduler.hpp index ceb16e198..bfc4b86d7 100644 --- a/Inc/HALAL/Services/Time/Scheduler.hpp +++ b/Inc/HALAL/Services/Time/Scheduler.hpp @@ -39,11 +39,17 @@ struct Scheduler { static constexpr uint32_t INVALID_ID = 2 * kMaxTasks; // temporary, will be removed - [[deprecated]] static inline void start() {} - static void update(); - static uint64_t get_global_tick(); - - static uint16_t register_task(uint32_t period_us, callback_t func); +<<<<<<< HEAD + [[deprecated]] static inline void start() {} + static void update(); + static inline uint64_t get_global_tick() { + if (Scheduler_global_timer == nullptr) { + return global_tick_us_; + } + return global_tick_us_ + Scheduler_global_timer->CNT; + } + + static uint16_t register_task(uint32_t period_us, callback_t func); static bool unregister_task(uint16_t id); static uint16_t set_timeout(uint32_t microseconds, callback_t func); diff --git a/Src/HALAL/Services/Time/RTC.cpp b/Src/HALAL/Services/Time/RTC.cpp index 6fbe0edb8..13e027559 100644 --- a/Src/HALAL/Services/Time/RTC.cpp +++ b/Src/HALAL/Services/Time/RTC.cpp @@ -1,7 +1,10 @@ #include "HALAL/Services/Time/RTC.hpp" +RTCData Global_RTC::global_RTC{}; + +#ifdef HAL_RTC_MODULE_ENABLED + RTC_HandleTypeDef hrtc; -RTCData Global_RTC::global_RTC; namespace { bool rtc_started = false; @@ -124,6 +127,7 @@ void Global_RTC::set_rtc_data( global_RTC = get_rtc_timestamp(); } } + void Global_RTC::update_rtc_data() { if (!ensure_started()) { return; @@ -134,3 +138,37 @@ void Global_RTC::update_rtc_data() { } global_RTC = get_rtc_timestamp(); } + +#else + +void Global_RTC::start_rtc() {} + +bool Global_RTC::ensure_started() { return false; } + +bool Global_RTC::has_valid_time() { return false; } + +void Global_RTC::update_rtc_data() {} + +RTCData Global_RTC::get_rtc_timestamp() { return global_RTC; } + +void Global_RTC::set_rtc_data( + uint16_t counter, + uint8_t second, + uint8_t minute, + uint8_t hour, + uint8_t day, + uint8_t month, + uint16_t year +) { + global_RTC = RTCData{ + .counter = counter, + .second = second, + .minute = minute, + .hour = hour, + .day = day, + .month = month, + .year = year, + }; +} + +#endif From a20de40e3aeb339ba467c987a60c7bccb02da3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Sun, 5 Apr 2026 11:51:23 +0200 Subject: [PATCH 04/23] Hooked to Board init --- Inc/ST-LIB.hpp | 6 ++++++ Src/ST-LIB.cpp | 10 ---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 8b6902f2b..2faf432a6 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -2,8 +2,10 @@ #include +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" #include "HALAL/HALAL.hpp" #include "ST-LIB_HIGH.hpp" +#include "ST-LIB_HIGH/Protections/FaultRuntime.hpp" #include "ST-LIB_LOW.hpp" class STLIB { @@ -306,6 +308,10 @@ template struct Board { cfg.dfsdm_clk_cfgs, GPIODomain::Init::instances ); + + Diagnostics::Runtime::install_default_sinks(); + FaultRuntime::install_default_broadcasters(); + ProtectionEngine::initialize(); // ... } diff --git a/Src/ST-LIB.cpp b/Src/ST-LIB.cpp index 4e1ca7dd4..f14bad9b8 100644 --- a/Src/ST-LIB.cpp +++ b/Src/ST-LIB.cpp @@ -1,6 +1,4 @@ #include "ST-LIB.hpp" -#include "HALAL/Services/Diagnostics/Diagnostics.hpp" -#include "ST-LIB_HIGH/Protections/FaultRuntime.hpp" #ifdef STLIB_ETH @@ -13,10 +11,6 @@ void STLIB::start( UART::Peripheral& printf_peripheral ) { HALAL::start(mac, ip, subnet_mask, gateway, printf_peripheral); - Diagnostics::Runtime::install_default_sinks(); - FaultRuntime::install_default_broadcasters(); - STLIB_LOW::start(); - STLIB_HIGH::start(); } void STLIB::start( @@ -33,10 +27,6 @@ void STLIB::start( void STLIB::start(UART::Peripheral& printf_peripheral) { HALAL::start(printf_peripheral); - Diagnostics::Runtime::install_default_sinks(); - FaultRuntime::install_default_broadcasters(); - STLIB_LOW::start(); - STLIB_HIGH::start(); } #endif // STLIB_ETH From c0fb7dd27ecb413f9658f31bb5adbb32b0c8c213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 8 Apr 2026 19:29:57 +0200 Subject: [PATCH 05/23] Harden fault, diagnostic, and reporter subsystems - FaultController: decouple FaultCause from DiagnosticRecord; add early-fault bootstrap so faults before start() are latched and the runtime starts directly in FAULT; remove FaultRuntime (superseded) - Diagnostics::Hub: fixed-capacity history ring and pending queue; flush_urgent() drains urgent records first; no heap in publish/flush - RecordFactory: explicit conversion FaultCause -> DiagnosticRecord via FaultDiagnosticMapper; runtime_panic / runtime_fault / runtime_warning / runtime_info factories with RuntimeSourceMetadata - Runtime reporters: PANIC/FAULT use std::source_location, format into fixed stack buffer, no shared mutable metadata (ISR-safe); WARNING/INFO go through RuntimeDiagnosticReporter; ErrorHandler/InfoWarning are now thin compatibility aliases - ProtectionEngine: evaluate() throttles fault notifications via notify_delay_in_microseconds; non-fatal rule edges published to Hub --- .../Services/Diagnostics/Diagnostics.hpp | 90 ++++++- .../Services/InfoWarning/InfoWarning.hpp | 69 ++--- .../Protections/FaultController.hpp | 212 +++++++++++---- Inc/ST-LIB_HIGH/Protections/FaultRuntime.hpp | 10 - .../Protections/ProtectionEngine.hpp | 12 +- Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp | 73 ++---- .../Diagnostics/DiagnosticFormatter.cpp | 4 +- .../DiagnosticTimestampProvider.cpp | 2 +- .../Services/Diagnostics/DiagnosticsHub.cpp | 198 +++++++++++--- .../Services/InfoWarning/InfoWarning.cpp | 76 ++++-- .../Protections/FaultController.cpp | 248 +++++++++++++++--- Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp | 68 ----- .../Protections/ProtectionEngine.cpp | 28 +- Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp | 65 +++-- 14 files changed, 769 insertions(+), 386 deletions(-) delete mode 100644 Inc/ST-LIB_HIGH/Protections/FaultRuntime.hpp delete mode 100644 Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp diff --git a/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp index 85ed8577c..301c12576 100644 --- a/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp +++ b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp @@ -3,6 +3,10 @@ #include "C++Utilities/CppUtils.hpp" #include "ST-LIB_HIGH/Protections/ProtectionTypes.hpp" +namespace ST_LIB::TestAccess { +struct DiagnosticsHub; +} + namespace Diagnostics { namespace Config { @@ -18,7 +22,14 @@ inline constexpr size_t formatted_message_capacity = 320; } // namespace Config enum class Severity : uint8_t { INFO = 0, WARNING, FAULT }; -enum class Category : uint8_t { RUNTIME_ERROR = 0, RUNTIME_WARNING, PROTECTION_EVENT }; +enum class DiagnosticPriority : uint8_t { NORMAL = 0, URGENT }; +enum class Category : uint8_t { + RUNTIME_PANIC = 0, + RUNTIME_FAULT, + RUNTIME_WARNING, + RUNTIME_INFO, + PROTECTION_EVENT +}; enum class RegistrationError : uint8_t { CAPACITY_EXCEEDED = 0, STORAGE_TOO_SMALL }; struct Timestamp { @@ -63,13 +74,20 @@ union DiagnosticPayload { }; struct DiagnosticRecord { + DiagnosticPriority priority{DiagnosticPriority::NORMAL}; Severity severity{Severity::INFO}; - Category category{Category::RUNTIME_WARNING}; + Category category{Category::RUNTIME_INFO}; Timestamp timestamp{}; char origin[Config::origin_capacity + 1]{}; DiagnosticPayload payload{}; }; +struct RuntimeSourceMetadata { + int line{0}; + const char* function_name{nullptr}; + const char* file_name{nullptr}; +}; + class DiagnosticSink { public: virtual ~DiagnosticSink() = default; @@ -86,6 +104,46 @@ class DiagnosticTimestampProvider { static Timestamp capture(); }; +namespace RecordFactory { + +DiagnosticRecord runtime_panic( + const char* message, + bool truncated, + const RuntimeSourceMetadata& metadata, + DiagnosticPriority priority = DiagnosticPriority::NORMAL +); + +DiagnosticRecord runtime_fault( + const char* message, + bool truncated, + const RuntimeSourceMetadata& metadata, + DiagnosticPriority priority = DiagnosticPriority::NORMAL +); + +DiagnosticRecord runtime_warning( + const char* message, + bool truncated, + const RuntimeSourceMetadata& metadata, + DiagnosticPriority priority = DiagnosticPriority::NORMAL +); + +DiagnosticRecord runtime_info( + const char* message, + bool truncated, + const RuntimeSourceMetadata& metadata, + DiagnosticPriority priority = DiagnosticPriority::NORMAL +); + +DiagnosticRecord protection_event( + const char* protection_name, + Protections::RuleState state, + Protections::RuleEdge edge, + const Protections::RuleSnapshot& snapshot, + DiagnosticPriority priority = DiagnosticPriority::NORMAL +); + +} // namespace RecordFactory + class Hub { public: template @@ -111,7 +169,14 @@ class Hub { } static void publish(DiagnosticRecord record); - static void publish_runtime_error( + static void publish_runtime_panic( + const char* message, + bool truncated, + int line, + const char* func, + const char* file + ); + static void publish_runtime_fault( const char* message, bool truncated, int line, @@ -125,6 +190,13 @@ class Hub { const char* func, const char* file ); + static void publish_runtime_info( + const char* message, + bool truncated, + int line, + const char* func, + const char* file + ); static void publish_protection_event( const char* protection_name, Protections::RuleState state, @@ -132,12 +204,11 @@ class Hub { const Protections::RuleSnapshot& snapshot ); static void flush(); - - static void clear_for_testing(); - static size_t history_size_for_testing(); - static size_t pending_size_for_testing(); + static void flush_urgent(); private: + friend struct ST_LIB::TestAccess::DiagnosticsHub; + struct PendingRecord { DiagnosticRecord record{}; uint8_t delivered_mask{0}; @@ -160,6 +231,8 @@ class Hub { static void push_history(const DiagnosticRecord& record); static void push_pending(const DiagnosticRecord& record); static void remove_pending(size_t index); + static size_t find_oldest_normal_pending(); + static void flush_pending(bool urgent_only); static array sink_storage; static array sinks; @@ -174,9 +247,10 @@ class Hub { class Runtime { public: static void install_default_sinks(); - static void reset_for_testing(); private: + friend struct ST_LIB::TestAccess::DiagnosticsHub; + static bool defaults_installed; }; diff --git a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp index cbd58ebf1..22a9acd82 100644 --- a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp +++ b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp @@ -1,53 +1,38 @@ -/* - * InfoWarning.hpp - * - * Created on: Jun 12, 2024 - * Author: gonzalo - */ - #pragma once -#include "C++Utilities/CppUtils.hpp" +#include -class InfoWarning { -private: - static int line; - static const char* func; - static const char* file; +#include "C++Utilities/CppUtils.hpp" +class RuntimeDiagnosticReporter { public: - /** - * @brief Triggers WarningHandler and format the warning message. The format works - * exactly like printf format. - * - * @param format String which will be formated. - * @param args Arguments specifying data to print - */ - static void InfoWarningTrigger(const char* format, ...); - - /** - * @brief Get all metadata needed for the warning message, including the line function and file. - * The default parameters are not necessary but are there in case the compiler macros - * stop working because a change of the compiler. - * - * @param line Line where the warning occurred - * @param func Function where the warning occurred - * @param file File where the warning occurred - */ - static void SetMetaData( - int line = __builtin_LINE(), - const char* func = __builtin_FUNCTION(), - const char* file = __builtin_FILE() + static void TriggerWarning( + const std::source_location& location, + const char* format, + ... ); - - /** - * @brief Transmit the warning message. - */ - static void InfoWarningUpdate(); + static void TriggerInfo( + const std::source_location& location, + const char* format, + ... + ); + static void Flush(); }; #define WARNING(x, ...) \ do { \ - InfoWarning::SetMetaData(__LINE__, __FUNCTION__, __FILE__); \ - InfoWarning::InfoWarningTrigger(x, ##__VA_ARGS__); \ + RuntimeDiagnosticReporter::TriggerWarning( \ + std::source_location::current(), \ + x __VA_OPT__(, ) __VA_ARGS__ \ + ); \ } while (0) + +#define INFO(x, ...) \ + do { \ + RuntimeDiagnosticReporter::TriggerInfo( \ + std::source_location::current(), \ + x __VA_OPT__(, ) __VA_ARGS__ \ + ); \ + } while (0) + +using InfoWarning = RuntimeDiagnosticReporter; diff --git a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp index b0047f5cb..4dc2e5e3b 100644 --- a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp +++ b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp @@ -1,75 +1,181 @@ #pragma once #include "C++Utilities/CppUtils.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionTypes.hpp" #include "StateMachine/StateMachine.hpp" -class FaultBroadcaster { -public: - virtual ~FaultBroadcaster() = default; - virtual bool broadcast_fault() = 0; +namespace FaultConfig { +inline constexpr size_t origin_capacity = Protections::Config::max_name_length; +inline constexpr size_t runtime_message_capacity = 160; +inline constexpr size_t function_capacity = 64; +inline constexpr size_t file_capacity = 96; +} // namespace FaultConfig + +enum class FaultCauseKind : uint8_t { PANIC = 0, RUNTIME_FAULT, PROTECTION, EXTERNAL }; + +struct FaultRuntimePayload { + uint32_t line{0}; + bool truncated{false}; + char message[FaultConfig::runtime_message_capacity + 1]{}; + char function_name[FaultConfig::function_capacity + 1]{}; + char file_name[FaultConfig::file_capacity + 1]{}; +}; + +struct FaultProtectionPayload { + Protections::RuleEdge edge{Protections::RuleEdge::NONE}; + Protections::RuleSnapshot snapshot{}; }; -enum class FaultBroadcasterRegistrationError : uint8_t { - CAPACITY_EXCEEDED = 0, - STORAGE_TOO_SMALL, +struct FaultCause { + FaultCauseKind kind{FaultCauseKind::PANIC}; + char origin[FaultConfig::origin_capacity + 1]{}; + FaultRuntimePayload runtime{}; + FaultProtectionPayload protection_event{}; + + static FaultCause panic( + const char* message, + bool truncated, + int line, + const char* func, + const char* file + ); + static FaultCause runtime_fault( + const char* message, + bool truncated, + int line, + const char* func, + const char* file + ); + static inline FaultCause runtime_error( + const char* message, + bool truncated, + int line, + const char* func, + const char* file + ) { + return panic(message, truncated, line, func, file); + } + static FaultCause protection( + const char* protection_name, + Protections::RuleEdge edge, + const Protections::RuleSnapshot& snapshot + ); + static FaultCause external(const char* origin, const char* message); }; class FaultController { public: using state_id = uint8_t; - static constexpr size_t max_broadcasters = 4; - static constexpr size_t max_broadcaster_storage = 512; + static constexpr size_t max_runtime_storage = 2048; - template - static expected emplace_broadcaster( - Args&&... args - ) { - if (broadcaster_count >= max_broadcasters) { - return unexpected(FaultBroadcasterRegistrationError::CAPACITY_EXCEEDED); - } - if constexpr ( - sizeof(Broadcaster) > max_broadcaster_storage || - alignof(Broadcaster) > alignof(std::max_align_t)) { - return unexpected(FaultBroadcasterRegistrationError::STORAGE_TOO_SMALL); - } else { - BroadcasterStorage& slot = broadcaster_storage[broadcaster_count]; - auto* broadcaster = construct_at( - reinterpret_cast(slot.bytes.data()), - std::forward(args)... + template + static void install_runtime() { + static_assert( + requires { + { Policy::has_operational_machine } -> std::convertible_to; + { Policy::on_fault_enter } -> std::convertible_to; + }, + "Fault policy must expose has_operational_machine and on_fault_enter" + ); + + runtime_started = false; + faulted = false; + has_latched_cause = false; + latched_cause = {}; + reconstruct_runtime_machine(RuntimeState::OPERATIONAL); + } + + static void start(); + static void check_transitions(); + static void request_fault(const FaultCause& cause); + static bool is_faulted(); + static const FaultCause* latched_fault_cause(); + +private: + enum class RuntimeState : uint8_t { OPERATIONAL = 0, FAULT = 1 }; + + struct RuntimeStorage { + alignas(std::max_align_t) array bytes{}; + IStateMachine* machine{nullptr}; + void (*destroy)(IStateMachine*){nullptr}; + void (*start)(IStateMachine*){nullptr}; + void (*rebuild_as_fault)(){nullptr}; + }; + + template + static consteval auto build_runtime_machine() { + constexpr auto operational_state = make_state(RuntimeState::OPERATIONAL); + constexpr auto fault_state = make_state(RuntimeState::FAULT); + + if constexpr (Policy::has_operational_machine) { + auto nested = + StateMachineHelper::add_nesting(operational_state, Policy::operational_machine); + auto machine = make_state_machine( + InitialState, + StateMachineHelper::add_nested_machines(nested), + operational_state, + fault_state ); - slot.broadcaster = broadcaster; - slot.destroy = [](FaultBroadcaster* base) { - destroy_at(static_cast(base)); - }; - broadcasters[broadcaster_count++] = broadcaster; - return broadcaster; + machine.add_enter_action(&FaultController::on_fault_state_enter, fault_state); + return machine; + } else { + auto machine = make_state_machine(InitialState, operational_state, fault_state); + machine.add_enter_action(&FaultController::on_fault_state_enter, fault_state); + return machine; } } - static void link_state_machine(IStateMachine& general_state_machine, state_id fault_id); - static void enter_fault(); - static void enter_external_fault(); + template + static void emplace_runtime_machine() { + using RuntimeMachine = decltype(build_runtime_machine()); + static_assert( + sizeof(RuntimeMachine) <= max_runtime_storage, + "Fault runtime machine exceeds FaultController storage" + ); + static_assert( + alignof(RuntimeMachine) <= alignof(std::max_align_t), + "Fault runtime machine alignment exceeds FaultController storage alignment" + ); - static void clear_broadcasters_for_testing(); + constexpr auto runtime_prototype = build_runtime_machine(); + auto* machine = construct_at( + reinterpret_cast(runtime_storage.bytes.data()), + runtime_prototype + ); -private: - struct BroadcasterStorage { - alignas(std::max_align_t) array bytes{}; - FaultBroadcaster* broadcaster{nullptr}; - void (*destroy)(FaultBroadcaster*){nullptr}; - - void reset() { - if (broadcaster != nullptr && destroy != nullptr) { - destroy(broadcaster); - } - broadcaster = nullptr; - destroy = nullptr; + runtime_storage.machine = machine; + runtime_storage.destroy = [](IStateMachine* base) { + destroy_at(static_cast(base)); + }; + runtime_storage.start = [](IStateMachine* base) { + static_cast(base)->start(); + }; + + global_machine = machine; + } + + template static void reconstruct_runtime_machine(RuntimeState initial_state) { + reset_runtime_storage(); + if (initial_state == RuntimeState::FAULT) { + emplace_runtime_machine(); + } else { + emplace_runtime_machine(); } - }; + runtime_storage.rebuild_as_fault = []() { + reconstruct_runtime_machine(RuntimeState::FAULT); + }; + on_fault_enter = Policy::on_fault_enter; + } + + static void reset_runtime_storage(); + static void publish_fault_diagnostic(const FaultCause& cause); + static void on_fault_state_enter(); - static IStateMachine* general_state_machine; - static state_id fault_state_id; - static array broadcaster_storage; - static array broadcasters; - static size_t broadcaster_count; + static RuntimeStorage runtime_storage; + static IStateMachine* global_machine; + static Callback on_fault_enter; + static FaultCause latched_cause; + static bool has_latched_cause; + static bool faulted; + static bool runtime_started; }; diff --git a/Inc/ST-LIB_HIGH/Protections/FaultRuntime.hpp b/Inc/ST-LIB_HIGH/Protections/FaultRuntime.hpp deleted file mode 100644 index 6c603668c..000000000 --- a/Inc/ST-LIB_HIGH/Protections/FaultRuntime.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -class FaultRuntime { -public: - static void install_default_broadcasters(); - static void reset_for_testing(); - -private: - static bool defaults_installed; -}; diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp index 68528197a..c0cafe70f 100644 --- a/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp @@ -4,6 +4,10 @@ #include "ST-LIB_HIGH/Protections/FaultController.hpp" #include "ST-LIB_HIGH/Protections/Protection.hpp" +namespace ST_LIB::TestAccess { +struct ProtectionEngine; +} + namespace Protections { template class ProtectionHandle { @@ -46,8 +50,6 @@ using ProtectionVariant = variant< class ProtectionEngine { public: - using state_id = FaultController::state_id; - template static expected< Protections::ProtectionHandle::value_type>, @@ -74,12 +76,12 @@ class ProtectionEngine { static void initialize(); static void evaluate(); - static void link_state_machine(IStateMachine& general_state_machine, state_id fault_id); - static void clear_for_testing(); private: + friend struct ST_LIB::TestAccess::ProtectionEngine; + template - static void publish_fault_if_due( + static void request_fault_if_due( Protection& protection, const Protections::ProtectionEvaluation& evaluation ); diff --git a/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp b/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp index 0b87ddd8c..de61fdd7a 100644 --- a/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp +++ b/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp @@ -1,59 +1,44 @@ -/* - * ErrorHandler.hpp - * - * Created on: Dec 22, 2022 - * Author: Pablo - */ - #pragma once +#include + #include "C++Utilities/CppUtils.hpp" #ifndef SIM_ON #include "HALAL/Services/Communication/UART/UART.hpp" #endif // !defined(SIM_ON) -class ErrorHandlerModel { -private: - static int line; - static const char* func; - static const char* file; - +class PanicReporter { public: - /** - * @brief Triggers ErrorHandler and format the error message. The format works - * exactly like printf format. - * - * @param format String which will be formated. - * @param args Arguments specifying data to print - * @return uint8_t Id of the service. - */ - static void ErrorHandlerTrigger(const char* format, ...); - - /** - * @brief Get all metadata needed for the error message, including the line function and file. - * The default parameters are not necessary but are there in case the compiler macros - * stop working because a change of the compiler. - * - * @param line Line where the error occurred - * @param func Function where the error occurred - * @param file File where the file occurred - * @return uint8_t Id of the service. - */ - static void SetMetaData( - int line = __builtin_LINE(), - const char* func = __builtin_FUNCTION(), - const char* file = __builtin_FILE() + static void Trigger( + const std::source_location& location, + const char* format, + ... ); + static void Flush(); +}; - /** - * @brief Transmit the error message. - */ - static void ErrorHandlerUpdate(); +class FaultReporter { +public: + static void Trigger( + const std::source_location& location, + const char* format, + ... + ); + static void Flush(); }; -#define ErrorHandler(x, ...) \ +#define PANIC(x, ...) \ do { \ - ErrorHandlerModel::SetMetaData(__LINE__, __FUNCTION__, __FILE__); \ - ErrorHandlerModel::ErrorHandlerTrigger(x, ##__VA_ARGS__); \ + PanicReporter::Trigger(std::source_location::current(), x __VA_OPT__(, ) __VA_ARGS__); \ } while (0) + +#define FAULT(x, ...) \ + do { \ + FaultReporter::Trigger(std::source_location::current(), x __VA_OPT__(, ) __VA_ARGS__); \ + } while (0) + +using ErrorHandlerModel = PanicReporter; + +// Deprecated compatibility macro. +#define ErrorHandler(x, ...) PANIC(x __VA_OPT__(, ) __VA_ARGS__) diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp index 2860cd5b9..410ed67ad 100644 --- a/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp +++ b/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp @@ -258,8 +258,10 @@ void DiagnosticFormatter::describe( buffer[0] = '\0'; switch (record.category) { - case Category::RUNTIME_ERROR: + case Category::RUNTIME_PANIC: + case Category::RUNTIME_FAULT: case Category::RUNTIME_WARNING: + case Category::RUNTIME_INFO: format_runtime_record(record, buffer, buffer_size); return; case Category::PROTECTION_EVENT: diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp index 7e90ece81..6ffc9c1a8 100644 --- a/Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp +++ b/Src/HALAL/Services/Diagnostics/DiagnosticTimestampProvider.cpp @@ -12,7 +12,7 @@ Timestamp DiagnosticTimestampProvider::capture() { Timestamp timestamp{}; #if defined(HAL_RTC_MODULE_ENABLED) && !defined(SIM_ON) - if (Global_RTC::ensure_started() && Global_RTC::has_valid_time()) { + if (Global_RTC::is_started() && Global_RTC::has_valid_time()) { Global_RTC::update_rtc_data(); const RTCData& rtc = Global_RTC::global_RTC; timestamp.has_rtc = true; diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp index 997745e95..6811b1d7a 100644 --- a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp +++ b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp @@ -14,8 +14,10 @@ bool Runtime::defaults_installed = false; namespace { -constexpr const char* runtime_error_origin = "error_handler"; -constexpr const char* runtime_warning_origin = "info_warning"; +constexpr const char* runtime_panic_origin = "runtime_panic"; +constexpr const char* runtime_fault_origin = "runtime_fault"; +constexpr const char* runtime_warning_origin = "runtime_warning"; +constexpr const char* runtime_info_origin = "runtime_info"; size_t bounded_strnlen(const char* src, size_t max_length) { if (src == nullptr) { @@ -63,7 +65,12 @@ void Hub::push_history(const DiagnosticRecord& record) { void Hub::push_pending(const DiagnosticRecord& record) { if (pending_count == Config::pending_capacity) { - remove_pending(0); + if (record.priority == DiagnosticPriority::URGENT) { + const size_t normal_index = find_oldest_normal_pending(); + remove_pending(normal_index == pending_count ? 0 : normal_index); + } else { + remove_pending(0); + } } pending_records[pending_count].record = record; @@ -82,6 +89,15 @@ void Hub::remove_pending(size_t index) { pending_count--; } +size_t Hub::find_oldest_normal_pending() { + for (size_t index = 0; index < pending_count; ++index) { + if (pending_records[index].record.priority == DiagnosticPriority::NORMAL) { + return index; + } + } + return pending_count; +} + void Hub::publish(DiagnosticRecord record) { push_history(record); @@ -92,53 +108,95 @@ void Hub::publish(DiagnosticRecord record) { push_pending(record); } -void Hub::publish_runtime_error( +DiagnosticRecord RecordFactory::runtime_fault( const char* message, bool truncated, - int line, - const char* func, - const char* file + const RuntimeSourceMetadata& metadata, + DiagnosticPriority priority ) { DiagnosticRecord record{}; + record.priority = priority; record.severity = Severity::FAULT; - record.category = Category::RUNTIME_ERROR; + record.category = Category::RUNTIME_FAULT; record.timestamp = DiagnosticTimestampProvider::capture(); - copy_c_string(record.origin, runtime_error_origin); - record.payload.runtime.line = static_cast(line < 0 ? 0 : line); + copy_c_string(record.origin, runtime_fault_origin); + record.payload.runtime.line = static_cast(metadata.line < 0 ? 0 : metadata.line); record.payload.runtime.truncated = truncated; copy_c_string(record.payload.runtime.message, message, &record.payload.runtime.truncated); - copy_c_string(record.payload.runtime.function_name, func); - copy_c_string(record.payload.runtime.file_name, file); - publish(record); + copy_c_string(record.payload.runtime.function_name, metadata.function_name); + copy_c_string(record.payload.runtime.file_name, metadata.file_name); + return record; } -void Hub::publish_runtime_warning( +DiagnosticRecord RecordFactory::runtime_panic( const char* message, bool truncated, - int line, - const char* func, - const char* file + const RuntimeSourceMetadata& metadata, + DiagnosticPriority priority +) { + DiagnosticRecord record{}; + record.priority = priority; + record.severity = Severity::FAULT; + record.category = Category::RUNTIME_PANIC; + record.timestamp = DiagnosticTimestampProvider::capture(); + copy_c_string(record.origin, runtime_panic_origin); + record.payload.runtime.line = static_cast(metadata.line < 0 ? 0 : metadata.line); + record.payload.runtime.truncated = truncated; + copy_c_string(record.payload.runtime.message, message, &record.payload.runtime.truncated); + copy_c_string(record.payload.runtime.function_name, metadata.function_name); + copy_c_string(record.payload.runtime.file_name, metadata.file_name); + return record; +} + +DiagnosticRecord RecordFactory::runtime_warning( + const char* message, + bool truncated, + const RuntimeSourceMetadata& metadata, + DiagnosticPriority priority ) { DiagnosticRecord record{}; + record.priority = priority; record.severity = Severity::WARNING; record.category = Category::RUNTIME_WARNING; record.timestamp = DiagnosticTimestampProvider::capture(); copy_c_string(record.origin, runtime_warning_origin); - record.payload.runtime.line = static_cast(line < 0 ? 0 : line); + record.payload.runtime.line = static_cast(metadata.line < 0 ? 0 : metadata.line); record.payload.runtime.truncated = truncated; copy_c_string(record.payload.runtime.message, message, &record.payload.runtime.truncated); - copy_c_string(record.payload.runtime.function_name, func); - copy_c_string(record.payload.runtime.file_name, file); - publish(record); + copy_c_string(record.payload.runtime.function_name, metadata.function_name); + copy_c_string(record.payload.runtime.file_name, metadata.file_name); + return record; } -void Hub::publish_protection_event( +DiagnosticRecord RecordFactory::runtime_info( + const char* message, + bool truncated, + const RuntimeSourceMetadata& metadata, + DiagnosticPriority priority +) { + DiagnosticRecord record{}; + record.priority = priority; + record.severity = Severity::INFO; + record.category = Category::RUNTIME_INFO; + record.timestamp = DiagnosticTimestampProvider::capture(); + copy_c_string(record.origin, runtime_info_origin); + record.payload.runtime.line = static_cast(metadata.line < 0 ? 0 : metadata.line); + record.payload.runtime.truncated = truncated; + copy_c_string(record.payload.runtime.message, message, &record.payload.runtime.truncated); + copy_c_string(record.payload.runtime.function_name, metadata.function_name); + copy_c_string(record.payload.runtime.file_name, metadata.file_name); + return record; +} + +DiagnosticRecord RecordFactory::protection_event( const char* protection_name, Protections::RuleState state, Protections::RuleEdge edge, - const Protections::RuleSnapshot& snapshot + const Protections::RuleSnapshot& snapshot, + DiagnosticPriority priority ) { DiagnosticRecord record{}; + record.priority = priority; switch (state) { case Protections::RuleState::FAULT: record.severity = Severity::FAULT; @@ -165,15 +223,84 @@ void Hub::publish_protection_event( record.payload.protection.uses_warning_threshold = snapshot.uses_warning_threshold; record.payload.protection.time_window_s = snapshot.time_window_s; record.payload.protection.sample_rate_hz = snapshot.sample_rate_hz; - publish(record); + return record; } -void Hub::flush() { +void Hub::publish_runtime_fault( + const char* message, + bool truncated, + int line, + const char* func, + const char* file +) { + publish(RecordFactory::runtime_fault( + message, + truncated, + RuntimeSourceMetadata{line, func, file} + )); +} + +void Hub::publish_runtime_panic( + const char* message, + bool truncated, + int line, + const char* func, + const char* file +) { + publish(RecordFactory::runtime_panic( + message, + truncated, + RuntimeSourceMetadata{line, func, file} + )); +} + +void Hub::publish_runtime_warning( + const char* message, + bool truncated, + int line, + const char* func, + const char* file +) { + publish(RecordFactory::runtime_warning( + message, + truncated, + RuntimeSourceMetadata{line, func, file} + )); +} + +void Hub::publish_runtime_info( + const char* message, + bool truncated, + int line, + const char* func, + const char* file +) { + publish(RecordFactory::runtime_info( + message, + truncated, + RuntimeSourceMetadata{line, func, file} + )); +} + +void Hub::publish_protection_event( + const char* protection_name, + Protections::RuleState state, + Protections::RuleEdge edge, + const Protections::RuleSnapshot& snapshot +) { + publish(RecordFactory::protection_event(protection_name, state, edge, snapshot)); +} + +void Hub::flush_pending(bool urgent_only) { const uint8_t target_mask = sink_count == 0 ? 0 : static_cast((1u << sink_count) - 1u); for (size_t record_index = 0; record_index < pending_count;) { PendingRecord& pending_record = pending_records[record_index]; + if (urgent_only && pending_record.record.priority != DiagnosticPriority::URGENT) { + record_index++; + continue; + } for (size_t sink_index = 0; sink_index < sink_count; ++sink_index) { const uint8_t sink_mask = static_cast(1u << sink_index); @@ -193,22 +320,11 @@ void Hub::flush() { } } -void Hub::clear_for_testing() { - for (size_t sink_index = 0; sink_index < sink_count; ++sink_index) { - sink_storage[sink_index].reset(); - sinks[sink_index] = nullptr; - } - sink_count = 0; - history_count = 0; - history_next_index = 0; - pending_count = 0; - Runtime::reset_for_testing(); -} - -size_t Hub::history_size_for_testing() { return history_count; } - -size_t Hub::pending_size_for_testing() { return pending_count; } +void Hub::flush_urgent() { flush_pending(true); } -void Runtime::reset_for_testing() { defaults_installed = false; } +void Hub::flush() { + flush_urgent(); + flush_pending(false); +} } // namespace Diagnostics diff --git a/Src/HALAL/Services/InfoWarning/InfoWarning.cpp b/Src/HALAL/Services/InfoWarning/InfoWarning.cpp index 5cf8a7a68..711bc7f17 100644 --- a/Src/HALAL/Services/InfoWarning/InfoWarning.cpp +++ b/Src/HALAL/Services/InfoWarning/InfoWarning.cpp @@ -1,38 +1,64 @@ -/* - * InfoWarning.cpp - * - * Created on: Jun 12, 2024 - * Author: gonzalo - */ - #include "HALAL/Services/InfoWarning/InfoWarning.hpp" #include "HALAL/Services/Diagnostics/Diagnostics.hpp" -int InfoWarning::line = 0; -const char* InfoWarning::func = "Warning-No-Func-Found"; -const char* InfoWarning::file = "Warning-No-File-Found"; +namespace { + +void publish_runtime_diagnostic( + Diagnostics::Severity severity, + const std::source_location& location, + const char* format, + va_list arguments +) { + char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; + const int32_t written = vsnprintf(buffer, sizeof(buffer), format, arguments); -void InfoWarning::SetMetaData(int line, const char* func, const char* file) { - InfoWarning::line = line; - InfoWarning::func = func; - InfoWarning::file = file; + switch (severity) { + case Diagnostics::Severity::WARNING: + Diagnostics::Hub::publish_runtime_warning( + buffer, + written < 0 || static_cast(written) >= sizeof(buffer), + static_cast(location.line()), + location.function_name(), + location.file_name() + ); + return; + case Diagnostics::Severity::INFO: + Diagnostics::Hub::publish_runtime_info( + buffer, + written < 0 || static_cast(written) >= sizeof(buffer), + static_cast(location.line()), + location.function_name(), + location.file_name() + ); + return; + case Diagnostics::Severity::FAULT: + std::unreachable(); + } } -void InfoWarning::InfoWarningTrigger(const char* format, ...) { - char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; +} // namespace + +void RuntimeDiagnosticReporter::TriggerWarning( + const std::source_location& location, + const char* format, + ... +) { va_list arguments; va_start(arguments, format); - const int32_t written = vsnprintf(buffer, sizeof(buffer), format, arguments); + publish_runtime_diagnostic(Diagnostics::Severity::WARNING, location, format, arguments); va_end(arguments); +} - Diagnostics::Hub::publish_runtime_warning( - buffer, - written < 0 || static_cast(written) >= sizeof(buffer), - line, - func, - file - ); +void RuntimeDiagnosticReporter::TriggerInfo( + const std::source_location& location, + const char* format, + ... +) { + va_list arguments; + va_start(arguments, format); + publish_runtime_diagnostic(Diagnostics::Severity::INFO, location, format, arguments); + va_end(arguments); } -void InfoWarning::InfoWarningUpdate() { Diagnostics::Hub::flush(); } +void RuntimeDiagnosticReporter::Flush() { Diagnostics::Hub::flush(); } diff --git a/Src/ST-LIB_HIGH/Protections/FaultController.cpp b/Src/ST-LIB_HIGH/Protections/FaultController.cpp index 572c7ef74..87be2998f 100644 --- a/Src/ST-LIB_HIGH/Protections/FaultController.cpp +++ b/Src/ST-LIB_HIGH/Protections/FaultController.cpp @@ -1,55 +1,227 @@ #include "ST-LIB_HIGH/Protections/FaultController.hpp" #include "HALAL/Services/Diagnostics/Diagnostics.hpp" -#include "ST-LIB_HIGH/Protections/FaultRuntime.hpp" - -IStateMachine* FaultController::general_state_machine = nullptr; -FaultController::state_id FaultController::fault_state_id = 255; -array - FaultController::broadcaster_storage = {}; -array FaultController::broadcasters = {}; -size_t FaultController::broadcaster_count = 0; -bool FaultRuntime::defaults_installed = false; - -void FaultController::link_state_machine( - IStateMachine& general_state_machine, - FaultController::state_id fault_id -) { - FaultController::general_state_machine = &general_state_machine; - FaultController::fault_state_id = fault_id; -} - -void FaultController::enter_fault() { - if (general_state_machine == nullptr) { - Diagnostics::Hub::publish_runtime_error( - "FaultController does not have General State Machine linked", - false, - 0, - "FaultController::enter_fault", - __FILE__ + +namespace { + +static_assert(FaultConfig::origin_capacity == Diagnostics::Config::origin_capacity); +static_assert(FaultConfig::runtime_message_capacity == Diagnostics::Config::runtime_message_capacity); +static_assert(FaultConfig::function_capacity == Diagnostics::Config::function_capacity); +static_assert(FaultConfig::file_capacity == Diagnostics::Config::file_capacity); + +size_t bounded_strnlen(const char* src, size_t max_length) { + if (src == nullptr) { + return 0; + } + + size_t length = 0; + while (length < max_length && src[length] != '\0') { + length++; + } + return length; +} + +template +void copy_c_string(char (&dst)[Capacity], const char* src, bool* truncated = nullptr) { + if (Capacity == 0) { + if (truncated != nullptr) { + *truncated = true; + } + return; + } + + if (src == nullptr) { + dst[0] = '\0'; + return; + } + + const size_t length = bounded_strnlen(src, Capacity - 1); + memcpy(dst, src, length); + dst[length] = '\0'; + if (truncated != nullptr) { + *truncated = *truncated || src[length] != '\0'; + } +} + +namespace FaultDiagnosticMapper { + +Diagnostics::DiagnosticRecord to_record(const FaultCause& cause) { + switch (cause.kind) { + case FaultCauseKind::PANIC: + return Diagnostics::RecordFactory::runtime_panic( + cause.runtime.message, + cause.runtime.truncated, + Diagnostics::RuntimeSourceMetadata{ + static_cast(cause.runtime.line), + cause.runtime.function_name, + cause.runtime.file_name + }, + Diagnostics::DiagnosticPriority::URGENT + ); + case FaultCauseKind::RUNTIME_FAULT: + case FaultCauseKind::EXTERNAL: + return Diagnostics::RecordFactory::runtime_fault( + cause.runtime.message, + cause.runtime.truncated, + Diagnostics::RuntimeSourceMetadata{ + static_cast(cause.runtime.line), + cause.runtime.function_name, + cause.runtime.file_name + }, + Diagnostics::DiagnosticPriority::URGENT ); + case FaultCauseKind::PROTECTION: + return Diagnostics::RecordFactory::protection_event( + cause.origin, + Protections::RuleState::FAULT, + cause.protection_event.edge, + cause.protection_event.snapshot, + Diagnostics::DiagnosticPriority::URGENT + ); + } + + std::unreachable(); +} + +} // namespace FaultDiagnosticMapper + +} // namespace + +FaultController::RuntimeStorage FaultController::runtime_storage = {}; +IStateMachine* FaultController::global_machine = nullptr; +Callback FaultController::on_fault_enter = nullptr; +FaultCause FaultController::latched_cause = {}; +bool FaultController::has_latched_cause = false; +bool FaultController::faulted = false; +bool FaultController::runtime_started = false; + +FaultCause FaultCause::panic( + const char* message, + bool truncated, + int line, + const char* func, + const char* file +) { + FaultCause cause{}; + cause.kind = FaultCauseKind::PANIC; + copy_c_string(cause.origin, "runtime_panic"); + cause.runtime.line = static_cast(line < 0 ? 0 : line); + cause.runtime.truncated = truncated; + copy_c_string(cause.runtime.message, message, &cause.runtime.truncated); + copy_c_string(cause.runtime.function_name, func); + copy_c_string(cause.runtime.file_name, file); + return cause; +} + +FaultCause FaultCause::runtime_fault( + const char* message, + bool truncated, + int line, + const char* func, + const char* file +) { + FaultCause cause{}; + cause.kind = FaultCauseKind::RUNTIME_FAULT; + copy_c_string(cause.origin, "runtime_fault"); + cause.runtime.line = static_cast(line < 0 ? 0 : line); + cause.runtime.truncated = truncated; + copy_c_string(cause.runtime.message, message, &cause.runtime.truncated); + copy_c_string(cause.runtime.function_name, func); + copy_c_string(cause.runtime.file_name, file); + return cause; +} + +FaultCause FaultCause::protection( + const char* protection_name, + Protections::RuleEdge edge, + const Protections::RuleSnapshot& snapshot +) { + FaultCause cause{}; + cause.kind = FaultCauseKind::PROTECTION; + copy_c_string(cause.origin, protection_name); + cause.protection_event.edge = edge; + cause.protection_event.snapshot = snapshot; + return cause; +} + +FaultCause FaultCause::external(const char* origin, const char* message) { + FaultCause cause{}; + cause.kind = FaultCauseKind::EXTERNAL; + copy_c_string(cause.origin, origin); + cause.runtime.truncated = false; + cause.runtime.line = 0; + copy_c_string( + cause.runtime.message, + message == nullptr ? "external fault" : message, + &cause.runtime.truncated + ); + copy_c_string(cause.runtime.function_name, "FaultCause::external"); + copy_c_string(cause.runtime.file_name, __FILE__); + return cause; +} + +void FaultController::reset_runtime_storage() { + if (runtime_storage.machine != nullptr && runtime_storage.destroy != nullptr) { + runtime_storage.destroy(runtime_storage.machine); + } + runtime_storage.machine = nullptr; + runtime_storage.destroy = nullptr; + runtime_storage.start = nullptr; + runtime_storage.rebuild_as_fault = nullptr; + global_machine = nullptr; +} + +void FaultController::start() { + if (global_machine == nullptr || runtime_storage.start == nullptr || runtime_started) { return; } - if (general_state_machine->get_current_state_id() == fault_state_id) { + runtime_storage.start(global_machine); + runtime_started = true; + + if (faulted) { + Diagnostics::Hub::flush_urgent(); + } +} + +void FaultController::check_transitions() { + if (global_machine == nullptr || !runtime_started) { return; } + global_machine->check_transitions(); +} - general_state_machine->force_change_state(fault_state_id); - for (size_t broadcaster_index = 0; broadcaster_index < broadcaster_count; broadcaster_index++) { - if (broadcasters[broadcaster_index] != nullptr) { - broadcasters[broadcaster_index]->broadcast_fault(); +void FaultController::publish_fault_diagnostic(const FaultCause& cause) { + Diagnostics::Hub::publish(FaultDiagnosticMapper::to_record(cause)); + Diagnostics::Hub::flush_urgent(); +} + +void FaultController::request_fault(const FaultCause& cause) { + if (!faulted) { + latched_cause = cause; + has_latched_cause = true; + faulted = true; + + if (global_machine != nullptr) { + if (runtime_started) { + global_machine->force_change_state(static_cast(RuntimeState::FAULT)); + } else if (runtime_storage.rebuild_as_fault != nullptr) { + runtime_storage.rebuild_as_fault(); + } } } + + publish_fault_diagnostic(cause); } -void FaultController::enter_external_fault() { enter_fault(); } +bool FaultController::is_faulted() { return faulted; } + +const FaultCause* FaultController::latched_fault_cause() { + return has_latched_cause ? &latched_cause : nullptr; +} -void FaultController::clear_broadcasters_for_testing() { - for (size_t broadcaster_index = 0; broadcaster_index < broadcaster_count; broadcaster_index++) { - broadcaster_storage[broadcaster_index].reset(); - broadcasters[broadcaster_index] = nullptr; +void FaultController::on_fault_state_enter() { + if (on_fault_enter != nullptr) { + on_fault_enter(); } - broadcaster_count = 0; - FaultRuntime::reset_for_testing(); } diff --git a/Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp b/Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp deleted file mode 100644 index 51b178990..000000000 --- a/Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "ST-LIB_HIGH/Protections/FaultRuntime.hpp" - -#include "ST-LIB_HIGH/Protections/FaultController.hpp" - -#include "HALAL/Models/Packets/Order.hpp" -#include "HALAL/Services/Communication/FDCAN/FDCAN.hpp" - -namespace { - -constexpr uint16_t remote_fault_order_id = 0; - -void handle_remote_fault() { FaultController::enter_external_fault(); } - -StackOrder<0>& fault_order() { - static StackOrder<0> order(remote_fault_order_id, handle_remote_fault); - return order; -} - -#if defined(HAL_FDCAN_MODULE_ENABLED) && !defined(SIM_ON) -class FdcanFaultBroadcaster final : public FaultBroadcaster { -public: - bool broadcast_fault() override { - bool delivered = false; - for (const auto& [key, value] : FDCAN::registered_fdcan) { - (void)value; - delivered = FDCAN::transmit(key, FDCAN::ID::FAULT_ID, NULL) || delivered; - } - return delivered; - } -}; -#endif - -#ifdef STLIB_ETH -class OrderProtocolFaultBroadcaster final : public FaultBroadcaster { -public: - bool broadcast_fault() override { - bool delivered = false; - for (OrderProtocol* socket : OrderProtocol::sockets) { - if (socket == nullptr) { - continue; - } - delivered = socket->send_order(fault_order()) || delivered; - } - return delivered; - } -}; -#endif - -} // namespace - -void FaultRuntime::install_default_broadcasters() { - if (defaults_installed) { - return; - } - -#if defined(HAL_FDCAN_MODULE_ENABLED) && !defined(SIM_ON) - (void)FaultController::emplace_broadcaster(); -#endif - -#ifdef STLIB_ETH - (void)fault_order(); - (void)FaultController::emplace_broadcaster(); -#endif - - defaults_installed = true; -} - -void FaultRuntime::reset_for_testing() { defaults_installed = false; } diff --git a/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp b/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp index 8c6f3c6d6..5d9b520b2 100644 --- a/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp +++ b/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp @@ -21,15 +21,8 @@ void ProtectionEngine::initialize() { } } -void ProtectionEngine::link_state_machine( - IStateMachine& general_state_machine, - ProtectionEngine::state_id fault_id -) { - FaultController::link_state_machine(general_state_machine, fault_id); -} - template -void ProtectionEngine::publish_fault_if_due( +void ProtectionEngine::request_fault_if_due( Protection& protection, const Protections::ProtectionEvaluation& evaluation ) { @@ -44,12 +37,11 @@ void ProtectionEngine::publish_fault_if_due( return; } - Diagnostics::Hub::publish_protection_event( + FaultController::request_fault(FaultCause::protection( protection.get_name(), - Protections::RuleState::FAULT, evaluation.active_fault_edge, evaluation.active_fault_snapshot - ); + )); protection.set_last_fault_publish_tick(tick); } @@ -84,22 +76,10 @@ void ProtectionEngine::evaluate() { publish_edge_events(protection, evaluation); if (evaluation.has_active_fault) { - publish_fault_if_due(protection, evaluation); - FaultController::enter_fault(); + request_fault_if_due(protection, evaluation); } }, *protections[protection_index] ); } } - -void ProtectionEngine::clear_for_testing() { - for (size_t protection_index = 0; protection_index < protection_count; protection_index++) { - if (protections[protection_index].has_value()) { - visit([](auto& protection) { protection.clear_runtime_state(); }, *protections[protection_index]); - protections[protection_index].reset(); - } - } - protection_count = 0; - registration_locked = false; -} diff --git a/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp b/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp index 0cb0f2d7f..01a559e13 100644 --- a/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp +++ b/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp @@ -1,40 +1,53 @@ -/* - * ErrorHandler.cpp - * - * Created on: Dec 22, 2022 - * Author: Pablo - */ - #include "ErrorHandler/ErrorHandler.hpp" #include "HALAL/Services/Diagnostics/Diagnostics.hpp" #include "ST-LIB_HIGH/Protections/FaultController.hpp" -int ErrorHandlerModel::line = 0; -const char* ErrorHandlerModel::func = "Error-No-Func-Found"; -const char* ErrorHandlerModel::file = "Error-No-File-Found"; +namespace { + +void trigger_runtime_fatal( + bool panic, + const std::source_location& location, + const char* format, + va_list arguments +) { + char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; + const int32_t written = vsnprintf(buffer, sizeof(buffer), format, arguments); -void ErrorHandlerModel::SetMetaData(int line, const char* func, const char* file) { - ErrorHandlerModel::line = line; - ErrorHandlerModel::func = func; - ErrorHandlerModel::file = file; + if (panic) { + FaultController::request_fault(FaultCause::panic( + buffer, + written < 0 || static_cast(written) >= sizeof(buffer), + static_cast(location.line()), + location.function_name(), + location.file_name() + )); + } else { + FaultController::request_fault(FaultCause::runtime_fault( + buffer, + written < 0 || static_cast(written) >= sizeof(buffer), + static_cast(location.line()), + location.function_name(), + location.file_name() + )); + } } -void ErrorHandlerModel::ErrorHandlerTrigger(const char* format, ...) { - char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; +} // namespace + +void FaultReporter::Trigger(const std::source_location& location, const char* format, ...) { va_list arguments; va_start(arguments, format); - const int32_t written = vsnprintf(buffer, sizeof(buffer), format, arguments); + trigger_runtime_fatal(false, location, format, arguments); va_end(arguments); +} - Diagnostics::Hub::publish_runtime_error( - buffer, - written < 0 || static_cast(written) >= sizeof(buffer), - line, - func, - file - ); - FaultController::enter_fault(); +void PanicReporter::Trigger(const std::source_location& location, const char* format, ...) { + va_list arguments; + va_start(arguments, format); + trigger_runtime_fatal(true, location, format, arguments); + va_end(arguments); } -void ErrorHandlerModel::ErrorHandlerUpdate() { Diagnostics::Hub::flush(); } +void PanicReporter::Flush() { Diagnostics::Hub::flush(); } +void FaultReporter::Flush() { Diagnostics::Hub::flush(); } From 8d9109814eac2b714b61159ebdce00988becc2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 8 Apr 2026 19:30:24 +0200 Subject: [PATCH 06/23] Update peripheral callsites to PANIC/FAULT/WARNING/INFO macros Replace direct ErrorHandler / InfoWarning calls with the new macro facade (PANIC, FAULT, WARNING, INFO) across all HALAL peripheral drivers, Ethernet/TCP/UDP stacks, and service implementations. Fixes MDMA callsite that was using invalid string concatenation with a printf-style API: PANIC("MDMA Transfer Error, code: %lu", error_code). --- Inc/HALAL/Models/DMA/DMA2.hpp | 4 +- Inc/HALAL/Models/MDMA/MDMA.hpp | 4 +- Inc/HALAL/Models/MPUManager/MPUManager.hpp | 2 +- Inc/HALAL/Models/Packets/SPIOrder.hpp | 4 +- Inc/HALAL/Models/SPI/SPI2.hpp | 68 +++++++++---------- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 12 ++-- Inc/HALAL/Services/ADC/ADC.hpp | 14 ++-- .../Services/Communication/FDCAN/FDCAN.hpp | 2 +- Inc/HALAL/Services/Encoder/Encoder.hpp | 10 +-- Inc/HALAL/Services/PWM/DualPWM.hpp | 18 ++--- Inc/HALAL/Services/PWM/PWM.hpp | 8 +-- Inc/HALAL/Services/Time/RTC.hpp | 1 + Inc/HALAL/Services/Time/Scheduler.hpp | 1 - Inc/HALAL/Services/Watchdog/Watchdog.hpp | 4 +- Inc/ST-LIB_LOW/Sd/Sd.hpp | 26 +++---- Src/HALAL/HALAL.cpp | 2 +- Src/HALAL/Models/DMA/DMA.cpp | 4 +- Src/HALAL/Models/HALconfig/Halconfig.cpp | 6 +- .../Models/LowPowerTimer/LowPowerTimer.cpp | 2 +- Src/HALAL/Models/MDMA/MDMA.cpp | 20 +++--- Src/HALAL/Models/PinModel/Pin.cpp | 2 +- Src/HALAL/Models/SPI/SPI2.cpp | 20 +++--- .../Communication/Ethernet/LWIP/Ethernet.cpp | 8 +-- .../Ethernet/LWIP/TCP/ServerSocket.cpp | 8 +-- .../Ethernet/LWIP/TCP/Socket.cpp | 8 +-- .../Ethernet/LWIP/UDP/DatagramSocket.cpp | 8 +-- .../Services/Communication/FDCAN/FDCAN.cpp | 18 ++--- Src/HALAL/Services/Communication/I2C/I2C.cpp | 50 +++++++------- Src/HALAL/Services/Communication/SPI/SPI.cpp | 50 +++++++------- .../Services/Communication/UART/UART.cpp | 2 +- .../DigitalInputService.cpp | 2 +- .../DigitalOutputService.cpp | 10 +-- Src/HALAL/Services/FMAC/FMAC.cpp | 26 +++---- Src/HALAL/Services/Flash/Flash.cpp | 4 +- Src/HALAL/Services/Time/RTC.cpp | 14 ++-- Src/HALAL/Services/Time/Scheduler.cpp | 20 +++--- Src/ST-LIB_LOW/Sd/Sd.cpp | 8 +-- 37 files changed, 235 insertions(+), 235 deletions(-) diff --git a/Inc/HALAL/Models/DMA/DMA2.hpp b/Inc/HALAL/Models/DMA/DMA2.hpp index 2afc5b89d..7e003338a 100644 --- a/Inc/HALAL/Models/DMA/DMA2.hpp +++ b/Inc/HALAL/Models/DMA/DMA2.hpp @@ -498,7 +498,7 @@ struct DMADomain { instances[i].dma = {}; if (stream == Stream::none) { - ErrorHandler("DMA stream must be selected before init"); + PANIC("DMA stream must be selected before init"); continue; } @@ -507,7 +507,7 @@ struct DMADomain { if (HAL_DMA_Init(&instances[i].dma) != HAL_OK) { instances[i].dma = {}; - ErrorHandler("DMA Init failed"); + PANIC("DMA Init failed"); continue; } diff --git a/Inc/HALAL/Models/MDMA/MDMA.hpp b/Inc/HALAL/Models/MDMA/MDMA.hpp index f9fb97eda..e4987d893 100644 --- a/Inc/HALAL/Models/MDMA/MDMA.hpp +++ b/Inc/HALAL/Models/MDMA/MDMA.hpp @@ -146,7 +146,7 @@ class MDMA { void init_node(void* src, void* dst, size_t size) { if (size == 0) { - ErrorHandler("MDMA: zero-length transfer is invalid"); + PANIC("MDMA: zero-length transfer is invalid"); return; } @@ -220,7 +220,7 @@ class MDMA { nodeConfig.Init.BufferTransferLength = buf_len; if (HAL_MDMA_LinkedList_CreateNode(&node, &nodeConfig) != HAL_OK) { - ErrorHandler("Error creating linked list in MDMA"); + PANIC("Error creating linked list in MDMA"); } // HAL_MDMA_LinkedList_CreateNode only sets the request field in CTBR; diff --git a/Inc/HALAL/Models/MPUManager/MPUManager.hpp b/Inc/HALAL/Models/MPUManager/MPUManager.hpp index 0237e2c9b..1183bbdc5 100644 --- a/Inc/HALAL/Models/MPUManager/MPUManager.hpp +++ b/Inc/HALAL/Models/MPUManager/MPUManager.hpp @@ -12,7 +12,7 @@ class MPUManager { no_cached_ram_occupied_bytes = no_cached_ram_occupied_bytes + size; if (no_cached_ram_occupied_bytes > NO_CACHED_RAM_MAXIMUM_SPACE) { uint32_t excess_bytes = no_cached_ram_occupied_bytes - NO_CACHED_RAM_MAXIMUM_SPACE; - ErrorHandler("Maximum capacity on non cached ram heap exceeded by %d", excess_bytes); + PANIC("Maximum capacity on non cached ram heap exceeded by %d", excess_bytes); return nullptr; } return buffer; diff --git a/Inc/HALAL/Models/Packets/SPIOrder.hpp b/Inc/HALAL/Models/Packets/SPIOrder.hpp index 845fe7ce0..c667fb164 100644 --- a/Inc/HALAL/Models/Packets/SPIOrder.hpp +++ b/Inc/HALAL/Models/Packets/SPIOrder.hpp @@ -78,7 +78,7 @@ class SPIBaseOrder { SPIBaseOrder(uint16_t id, uint16_t master_data_size, uint16_t slave_data_size) : id(id), master_data_size(master_data_size), slave_data_size(slave_data_size) { if (id == 0) { - ErrorHandler( + PANIC( "Cannot use 0 as the SPIOrderID, as it is reserved to the no Order ready signal" ); } @@ -88,7 +88,7 @@ class SPIBaseOrder { payload_size = slave_data_size + PAYLOAD_OVERHEAD + PAYLOAD_TAIL; } if (payload_size > SPI_MAXIMUM_PAYLOAD_SIZE_BYTES) { - ErrorHandler( + PANIC( "Cannot declare SPIOrder %d as its size surpasses the maximum data size", id ); diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index 02b1433fd..a170ffa74 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -147,7 +147,7 @@ struct SPIDomain { if consteval { compile_error("Invalid prescaler value"); } else { - ErrorHandler("Invalid prescaler value"); + PANIC("Invalid prescaler value"); return SPI_BAUDRATEPRESCALER_256; } } @@ -589,7 +589,7 @@ struct SPIDomain { */ template bool send(span data) { if (data.size_bytes() % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data size (%d) not aligned to frame size (%d)", data.size_bytes(), frame_size @@ -613,7 +613,7 @@ struct SPIDomain { requires std::is_trivially_copyable_v { if (sizeof(T) % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data type size (%d) not aligned to frame size (%d)", sizeof(T), frame_size @@ -634,7 +634,7 @@ struct SPIDomain { */ template bool receive(span data) { if (data.size_bytes() % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data size (%d) not aligned to frame size (%d)", data.size_bytes(), frame_size @@ -658,7 +658,7 @@ struct SPIDomain { requires std::is_trivially_copyable_v { if (sizeof(T) % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data type size (%d) not aligned to frame size (%d)", sizeof(T), frame_size @@ -681,7 +681,7 @@ struct SPIDomain { bool transceive(span tx_data, span rx_data) { size_t size = std::min(tx_data.size_bytes(), rx_data.size_bytes()); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -707,7 +707,7 @@ struct SPIDomain { { size_t size = std::min(tx_data.size_bytes(), sizeof(T)); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -733,7 +733,7 @@ struct SPIDomain { { size_t size = std::min(sizeof(T), rx_data.size_bytes()); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -759,7 +759,7 @@ struct SPIDomain { { size_t size = std::min(sizeof(T1), sizeof(T2)); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -784,7 +784,7 @@ struct SPIDomain { bool send_DMA(span data, volatile bool* operation_flag = nullptr) { spi_instance.operation_flag = operation_flag; if (data.size_bytes() % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data size (%d) not aligned to frame size (%d)", data.size_bytes(), frame_size @@ -809,7 +809,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data size (%d) not aligned to frame size (%d)", sizeof(T), frame_size @@ -832,7 +832,7 @@ struct SPIDomain { bool receive_DMA(span data, volatile bool* operation_flag = nullptr) { spi_instance.operation_flag = operation_flag; if (data.size_bytes() % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data size (%d) not aligned to frame size (%d)", data.size_bytes(), frame_size @@ -857,7 +857,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data size (%d) not aligned to frame size (%d)", sizeof(T), frame_size @@ -885,7 +885,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(tx_data.size_bytes(), rx_data.size_bytes()); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -912,7 +912,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(tx_data.size_bytes(), sizeof(T)); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -943,7 +943,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(sizeof(T), rx_data.size_bytes()); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -970,7 +970,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(sizeof(T1), sizeof(T2)); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -994,7 +994,7 @@ struct SPIDomain { } else if (error_code == HAL_BUSY) { return false; } else { - ErrorHandler("SPI transmit error: %u", static_cast(error_code)); + PANIC("SPI transmit error: %u", static_cast(error_code)); return false; } } @@ -1048,7 +1048,7 @@ struct SPIDomain { bool listen(span data, volatile bool* operation_flag = nullptr) { spi_instance.operation_flag = operation_flag; if (data.size_bytes() % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data size (%d) not aligned to frame size (%d)", data.size_bytes(), frame_size @@ -1073,7 +1073,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data size (%d) not aligned to frame size (%d)", sizeof(T), frame_size @@ -1096,7 +1096,7 @@ struct SPIDomain { bool arm(span tx_data, volatile bool* operation_flag = nullptr) { spi_instance.operation_flag = operation_flag; if (tx_data.size_bytes() % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data size (%d) not aligned to frame size (%d)", tx_data.size_bytes(), frame_size @@ -1121,7 +1121,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - ErrorHandler( + PANIC( "SPI data size (%d) not aligned to frame size (%d)", sizeof(T), frame_size @@ -1149,7 +1149,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(tx_data.size_bytes(), rx_data.size_bytes()); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -1176,7 +1176,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(tx_data.size_bytes(), sizeof(T)); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -1204,7 +1204,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(sizeof(T), rx_data.size_bytes()); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -1231,7 +1231,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(sizeof(T1), sizeof(T2)); if (size % frame_size != 0) { - ErrorHandler( + PANIC( "SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size @@ -1255,7 +1255,7 @@ struct SPIDomain { } else if (error_code == HAL_BUSY) { return false; } else { - ErrorHandler("SPI transmit error: %u", static_cast(error_code)); + PANIC("SPI transmit error: %u", static_cast(error_code)); return false; } } @@ -1346,7 +1346,7 @@ struct SPIDomain { PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI1; PeriphClkInitStruct.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - ErrorHandler("Unable to configure SPI1 clock"); + PANIC("Unable to configure SPI1 clock"); } __HAL_RCC_SPI1_CLK_ENABLE(); spi_number = 1; @@ -1354,7 +1354,7 @@ struct SPIDomain { PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI2; PeriphClkInitStruct.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - ErrorHandler("Unable to configure SPI2 clock"); + PANIC("Unable to configure SPI2 clock"); } __HAL_RCC_SPI2_CLK_ENABLE(); spi_number = 2; @@ -1362,7 +1362,7 @@ struct SPIDomain { PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI3; PeriphClkInitStruct.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - ErrorHandler("Unable to configure SPI3 clock"); + PANIC("Unable to configure SPI3 clock"); } __HAL_RCC_SPI3_CLK_ENABLE(); spi_number = 3; @@ -1370,7 +1370,7 @@ struct SPIDomain { PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI4; PeriphClkInitStruct.Spi45ClockSelection = RCC_SPI45CLKSOURCE_PLL2; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - ErrorHandler("Unable to configure SPI4 clock"); + PANIC("Unable to configure SPI4 clock"); } __HAL_RCC_SPI4_CLK_ENABLE(); spi_number = 4; @@ -1378,7 +1378,7 @@ struct SPIDomain { PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI5; PeriphClkInitStruct.Spi45ClockSelection = RCC_SPI45CLKSOURCE_PLL2; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - ErrorHandler("Unable to configure SPI5 clock"); + PANIC("Unable to configure SPI5 clock"); } __HAL_RCC_SPI5_CLK_ENABLE(); spi_number = 5; @@ -1386,7 +1386,7 @@ struct SPIDomain { PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SPI6; PeriphClkInitStruct.Spi6ClockSelection = RCC_SPI6CLKSOURCE_PLL2; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - ErrorHandler("Unable to configure SPI6 clock"); + PANIC("Unable to configure SPI6 clock"); } __HAL_RCC_SPI6_CLK_ENABLE(); spi_number = 6; @@ -1473,7 +1473,7 @@ struct SPIDomain { init.IOSwap = SPIConfigTypes::translate_io_swap(e.config.io_swap); if (HAL_SPI_Init(&hspi) != HAL_OK) { - ErrorHandler("Unable to init SPI%u", spi_number); + PANIC("Unable to init SPI%u", spi_number); return; } diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index f4251e82e..284f1af7e 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -378,11 +378,11 @@ struct TimerDomain { SET_BIT(RCC->b, RCC_##b##_TIM##n##EN); \ } - if (false) { - } - TimerXList else { - ErrorHandler("Invalid timer given to rcc_enable_timer"); - } + if (false) { + } + TimerXList else { + PANIC("Invalid timer given to rcc_enable_timer"); + } #undef X } @@ -1223,6 +1223,6 @@ TimerDomain::Timer::get_gpio_af(ST_LIB::TimerRequest req, ST_LIB::TimerPin pin) sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if(HAL_TIMEx_MasterConfigSynchronization(handle, &sMasterConfig) != HAL_OK) { - ErrorHandler("Unable to configure master synch on %s", e.name); + PANIC("Unable to configure master synch on %s", e.name); } */ diff --git a/Inc/HALAL/Services/ADC/ADC.hpp b/Inc/HALAL/Services/ADC/ADC.hpp index 7356a89b1..2a39a4e95 100644 --- a/Inc/HALAL/Services/ADC/ADC.hpp +++ b/Inc/HALAL/Services/ADC/ADC.hpp @@ -832,11 +832,11 @@ struct ADCDomain { const auto buffer_size = buffer_size_for(peripheral); const auto buffer_offset = buffer_offset_for(peripheral); if (buffer_size == 0U) { - ErrorHandler("ADC DMA buffer not available"); + PANIC("ADC DMA buffer not available"); return nullptr; } if ((buffer_offset + buffer_size) > total_dma_slots) { - ErrorHandler("ADC DMA pool overflow"); + PANIC("ADC DMA pool overflow"); return nullptr; } @@ -908,7 +908,7 @@ struct ADCDomain { for (std::size_t i = 0; i < N; ++i) { const auto& cfg = runtime_cfgs[i]; if (!is_resolved_config(cfg)) { - ErrorHandler("ADC config unresolved (AUTO)"); + PANIC("ADC config unresolved (AUTO)"); continue; } instance_cfg_valid[i] = true; @@ -942,7 +942,7 @@ struct ADCDomain { DMADomain::Instance* dma_instance = find_dma_instance(first_cfg->dma_request, dma_peripherals); if (dma_instance == nullptr) { - ErrorHandler("ADC DMA instance unavailable"); + PANIC("ADC DMA instance unavailable"); continue; } uint16_t* buffer = get_dma_buffer(peripheral); @@ -955,7 +955,7 @@ struct ADCDomain { configure_peripheral(*first_cfg, channel_count); if (HAL_ADC_Init(hadc) != HAL_OK) { - ErrorHandler("ADC Init failed"); + PANIC("ADC Init failed"); continue; } @@ -978,7 +978,7 @@ struct ADCDomain { #endif if (HAL_ADC_ConfigChannel(hadc, &sConfig) != HAL_OK) { - ErrorHandler("ADC channel configuration failed"); + PANIC("ADC channel configuration failed"); config_error = true; break; } @@ -991,7 +991,7 @@ struct ADCDomain { if (HAL_ADC_Start_DMA(hadc, reinterpret_cast(buffer), channel_count) != HAL_OK) { - ErrorHandler("ADC DMA start failed"); + PANIC("ADC DMA start failed"); continue; } diff --git a/Inc/HALAL/Services/Communication/FDCAN/FDCAN.hpp b/Inc/HALAL/Services/Communication/FDCAN/FDCAN.hpp index f6754716a..f50027102 100644 --- a/Inc/HALAL/Services/Communication/FDCAN/FDCAN.hpp +++ b/Inc/HALAL/Services/Communication/FDCAN/FDCAN.hpp @@ -144,7 +144,7 @@ class FDCAN { template uint8_t FDCAN::inscribe(FDCAN::Peripheral& fdcan) { if (!FDCAN::available_fdcans.contains(fdcan)) { - ErrorHandler( + PANIC( " The FDCAN peripheral %d is already used or does not exists.", (uint16_t)fdcan ); diff --git a/Inc/HALAL/Services/Encoder/Encoder.hpp b/Inc/HALAL/Services/Encoder/Encoder.hpp index 8e1ddd1b9..5ff09b331 100644 --- a/Inc/HALAL/Services/Encoder/Encoder.hpp +++ b/Inc/HALAL/Services/Encoder/Encoder.hpp @@ -50,7 +50,7 @@ template class Encoder { sConfig.IC2Filter = 0; if (HAL_TIM_Encoder_Init(tim->instance->hal_tim, &sConfig) != HAL_OK) { - ErrorHandler("Unable to init encoder"); + PANIC("Unable to init encoder"); return; } @@ -58,7 +58,7 @@ template class Encoder { sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(tim->instance->hal_tim, &sMasterConfig) != HAL_OK) { - ErrorHandler("Unable to config master synchronization in encoder"); + PANIC("Unable to config master synchronization in encoder"); return; } @@ -76,11 +76,11 @@ template class Encoder { return; if (HAL_TIM_Encoder_GetState(timer->instance->hal_tim) == HAL_TIM_STATE_RESET) { - ErrorHandler("Unable to get state from encoder"); + PANIC("Unable to get state from encoder"); return; } if (HAL_TIM_Encoder_Start(timer->instance->hal_tim, TIM_CHANNEL_ALL) != HAL_OK) { - ErrorHandler("Unable to start encoder"); + PANIC("Unable to start encoder"); return; } is_on = true; @@ -92,7 +92,7 @@ template class Encoder { return; if (HAL_TIM_Encoder_Stop(timer->instance->hal_tim, TIM_CHANNEL_ALL) != HAL_OK) { - ErrorHandler("Unable to stop encoder"); + PANIC("Unable to stop encoder"); return; } is_on = false; diff --git a/Inc/HALAL/Services/PWM/DualPWM.hpp b/Inc/HALAL/Services/PWM/DualPWM.hpp index b0121ec3c..bd8f27ca9 100644 --- a/Inc/HALAL/Services/PWM/DualPWM.hpp +++ b/Inc/HALAL/Services/PWM/DualPWM.hpp @@ -70,11 +70,11 @@ class DualPWM { if (this->is_on_positive) return; - volatile HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim - ->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; + volatile HAL_TIM_ChannelStateTypeDef* state = + &timer->instance->hal_tim + ->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; if (*state != HAL_TIM_CHANNEL_STATE_READY) { - ErrorHandler("Channel not ready"); + PANIC("Channel not ready"); } *state = HAL_TIM_CHANNEL_STATE_BUSY; @@ -104,11 +104,11 @@ class DualPWM { if (this->is_on_negative) return; - volatile HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim - ->ChannelNState[TimerDomain::get_channel_state_idx(negated_pin.channel)]; + volatile HAL_TIM_ChannelStateTypeDef* state = + &timer->instance->hal_tim + ->ChannelNState[TimerDomain::get_channel_state_idx(negated_pin.channel)]; if (*state != HAL_TIM_CHANNEL_STATE_READY) { - ErrorHandler("Channel not ready"); + PANIC("Channel not ready"); } *state = HAL_TIM_CHANNEL_STATE_BUSY; @@ -251,7 +251,7 @@ class DualPWM { sBreakDeadTimeConfig.DeadTime = 0b1110'0000 | (uint32_t)((float)time / (16 * clock_period_ns) - 32); } else { - ErrorHandler("Invalid dead time configuration"); + PANIC("Invalid dead time configuration"); } // sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; diff --git a/Inc/HALAL/Services/PWM/PWM.hpp b/Inc/HALAL/Services/PWM/PWM.hpp index 305e4251c..05c1fe30e 100644 --- a/Inc/HALAL/Services/PWM/PWM.hpp +++ b/Inc/HALAL/Services/PWM/PWM.hpp @@ -52,11 +52,11 @@ template class PWM { if (this->is_on) return; - volatile HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim - ->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; + volatile HAL_TIM_ChannelStateTypeDef* state = + &timer->instance->hal_tim + ->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; if (*state != HAL_TIM_CHANNEL_STATE_READY) { - ErrorHandler("Channel not ready"); + PANIC("Channel not ready"); } *state = HAL_TIM_CHANNEL_STATE_BUSY; diff --git a/Inc/HALAL/Services/Time/RTC.hpp b/Inc/HALAL/Services/Time/RTC.hpp index 9e12e0dc9..c000ba32c 100644 --- a/Inc/HALAL/Services/Time/RTC.hpp +++ b/Inc/HALAL/Services/Time/RTC.hpp @@ -19,6 +19,7 @@ class Global_RTC { public: static RTCData global_RTC; static void start_rtc(); + static bool is_started(); static bool ensure_started(); static bool has_valid_time(); static void update_rtc_data(); diff --git a/Inc/HALAL/Services/Time/Scheduler.hpp b/Inc/HALAL/Services/Time/Scheduler.hpp index bfc4b86d7..c7a508cd9 100644 --- a/Inc/HALAL/Services/Time/Scheduler.hpp +++ b/Inc/HALAL/Services/Time/Scheduler.hpp @@ -39,7 +39,6 @@ struct Scheduler { static constexpr uint32_t INVALID_ID = 2 * kMaxTasks; // temporary, will be removed -<<<<<<< HEAD [[deprecated]] static inline void start() {} static void update(); static inline uint64_t get_global_tick() { diff --git a/Inc/HALAL/Services/Watchdog/Watchdog.hpp b/Inc/HALAL/Services/Watchdog/Watchdog.hpp index 4a64476c5..071dda676 100644 --- a/Inc/HALAL/Services/Watchdog/Watchdog.hpp +++ b/Inc/HALAL/Services/Watchdog/Watchdog.hpp @@ -19,10 +19,10 @@ class Watchdog { static void start() { if ((chrono::duration_cast(watchdog_time)).count() > 32000000) { - ErrorHandler("Watchdog refresh interval is too big"); + PANIC("Watchdog refresh interval is too big"); } if ((chrono::duration_cast(watchdog_time)).count() < 125) { - ErrorHandler("Watchdog refresh interval is too short"); + PANIC("Watchdog refresh interval is too short"); } uint64_t milliseconds = chrono::duration_cast(watchdog_time).count(); uint32_t RL = double(milliseconds) * 8.0 - 1; // this is the formula for the Reload diff --git a/Inc/ST-LIB_LOW/Sd/Sd.hpp b/Inc/ST-LIB_LOW/Sd/Sd.hpp index b2eb5f00d..c4b65c9d2 100644 --- a/Inc/ST-LIB_LOW/Sd/Sd.hpp +++ b/Inc/ST-LIB_LOW/Sd/Sd.hpp @@ -341,7 +341,7 @@ struct SdDomain { check_cd_wp(); bool success = instance.initialize_card(); if (!success) { - ErrorHandler("SD Card initialization failed"); + PANIC("SD Card initialization failed"); } } @@ -349,7 +349,7 @@ struct SdDomain { check_cd_wp(); bool success = instance.deinitialize_card(); if (!success) { - ErrorHandler("SD Card deinitialization failed"); + PANIC("SD Card deinitialization failed"); } } @@ -358,10 +358,10 @@ struct SdDomain { bool read_blocks(uint32_t start_block, uint32_t num_blocks, bool* operation_complete_flag) { check_cd_wp(); if (!instance.card_initialized) { - ErrorHandler("SD Card not initialized"); + PANIC("SD Card not initialized"); } if (num_blocks > instance.mpu_buffer0_instance->size / 512) { - ErrorHandler("Too many blocks requested to read from SD"); + PANIC("Too many blocks requested to read from SD"); } if (HAL_SD_GetCardState(&instance.hsd) != HAL_SD_CARD_TRANSFER) { @@ -374,7 +374,7 @@ struct SdDomain { instance.Not_HAL_SDEx_ReadBlocksDMAMultiBuffer(start_block, num_blocks); if (status != HAL_OK) { - ErrorHandler("SD Card read operation failed"); + PANIC("SD Card read operation failed"); } instance.operation_flag = operation_complete_flag; @@ -387,10 +387,10 @@ struct SdDomain { write_blocks(uint32_t start_block, uint32_t num_blocks, bool* operation_complete_flag) { check_cd_wp(); if (!instance.card_initialized) { - ErrorHandler("SD Card not initialized"); + PANIC("SD Card not initialized"); } if (num_blocks > instance.mpu_buffer0_instance->size / 512) { - ErrorHandler("Too many blocks requested to write in SD"); + PANIC("Too many blocks requested to write in SD"); } if (HAL_SD_GetCardState(&instance.hsd) != HAL_SD_CARD_TRANSFER) { @@ -402,7 +402,7 @@ struct SdDomain { instance.Not_HAL_SDEx_WriteBlocksDMAMultiBuffer(start_block, num_blocks); if (status != HAL_OK) { - ErrorHandler("SD Card write operation failed"); + PANIC("SD Card write operation failed"); } instance.operation_flag = operation_complete_flag; @@ -433,12 +433,12 @@ struct SdDomain { void check_cd_wp() { if constexpr (has_cd) { if (!instance.is_card_present()) { - ErrorHandler("SD Card not present"); + PANIC("SD Card not present"); } } if constexpr (has_wp) { if (instance.is_write_protected()) { - ErrorHandler("SD Card is write-protected"); + PANIC("SD Card is write-protected"); } } } @@ -508,7 +508,7 @@ struct SdDomain { 2; // Round up to ensure frequency is not higher than target if (translated_clock_div > 1023) { - ErrorHandler("SDMMC clock divider too high, cannot achieve target frequency " + PANIC("SDMMC clock divider too high, cannot achieve target frequency " "with current PLL1 Q clock"); } @@ -531,7 +531,7 @@ struct SdDomain { RCC_PeriphCLKInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SDMMC; RCC_PeriphCLKInitStruct.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL; if (HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct) != HAL_OK) { - ErrorHandler("SDMMC clock configuration failed, maybe try with a slower clock or " + PANIC("SDMMC clock configuration failed, maybe try with a slower clock or " "higher divider?"); } @@ -539,7 +539,7 @@ struct SdDomain { __HAL_RCC_PLLCLKOUT_ENABLE(RCC_PLL1_DIVQ); if (HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SDMMC) == 0) { - ErrorHandler("SDMMC clock frequency is 0"); + PANIC("SDMMC clock frequency is 0"); } } }; diff --git a/Src/HALAL/HALAL.cpp b/Src/HALAL/HALAL.cpp index bc38c0609..1f43c8d58 100644 --- a/Src/HALAL/HALAL.cpp +++ b/Src/HALAL/HALAL.cpp @@ -48,7 +48,7 @@ static void common_start(UART::Peripheral& printf_peripheral) { CORDIC_HandleTypeDef hcordic; hcordic.Instance = CORDIC; if (HAL_CORDIC_Init(&hcordic) != HAL_OK) { - ErrorHandler("Unable to init CORDIC"); + PANIC("Unable to init CORDIC"); } #endif diff --git a/Src/HALAL/Models/DMA/DMA.cpp b/Src/HALAL/Models/DMA/DMA.cpp index 21b6e911d..9351d56e6 100644 --- a/Src/HALAL/Models/DMA/DMA.cpp +++ b/Src/HALAL/Models/DMA/DMA.cpp @@ -29,7 +29,7 @@ vector DMA::inscribed_streams = {}; void DMA::inscribe_stream() { if (available_streams.empty()) { - ErrorHandler("There are not any DMA Streams availables"); + PANIC("There are not any DMA Streams availables"); return; } inscribed_streams.push_back(available_streams.back()); @@ -39,7 +39,7 @@ void DMA::inscribe_stream() { void DMA::inscribe_stream(Stream dma_stream) { if (std::find(available_streams.begin(), available_streams.end(), dma_stream) == available_streams.end()) { - ErrorHandler("The DMA stream %d is not available", dma_stream); + PANIC("The DMA stream %d is not available", dma_stream); return; } inscribed_streams.push_back(dma_stream); diff --git a/Src/HALAL/Models/HALconfig/Halconfig.cpp b/Src/HALAL/Models/HALconfig/Halconfig.cpp index 14c0d9c3b..f7b2a7073 100644 --- a/Src/HALAL/Models/HALconfig/Halconfig.cpp +++ b/Src/HALAL/Models/HALconfig/Halconfig.cpp @@ -52,7 +52,7 @@ void HALconfig::system_clock() { #endif if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { - ErrorHandler("The RCC Osc config did not start correctly"); + PANIC("The RCC Osc config did not start correctly"); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | @@ -66,7 +66,7 @@ void HALconfig::system_clock() { RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2; RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK) { - ErrorHandler("The RCC clock config did not start correctly"); + PANIC("The RCC clock config did not start correctly"); } } @@ -121,6 +121,6 @@ void HALconfig::peripheral_clock() { #endif if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { - ErrorHandler("The RCCEx peripheral clock did not start correctly"); + PANIC("The RCCEx peripheral clock did not start correctly"); } } diff --git a/Src/HALAL/Models/LowPowerTimer/LowPowerTimer.cpp b/Src/HALAL/Models/LowPowerTimer/LowPowerTimer.cpp index f1632f92b..388c99479 100644 --- a/Src/HALAL/Models/LowPowerTimer/LowPowerTimer.cpp +++ b/Src/HALAL/Models/LowPowerTimer/LowPowerTimer.cpp @@ -20,6 +20,6 @@ void LowPowerTimer::init() { handle.Init.Input2Source = LPTIM_INPUT2SOURCE_GPIO; if (HAL_LPTIM_Init(&handle) != HAL_OK) { - ErrorHandler("The LPTIM %s could not be registered", name); + PANIC("The LPTIM %s could not be registered", name); } } diff --git a/Src/HALAL/Models/MDMA/MDMA.cpp b/Src/HALAL/Models/MDMA/MDMA.cpp index 9dc71f1a7..5fbd8a71b 100644 --- a/Src/HALAL/Models/MDMA/MDMA.cpp +++ b/Src/HALAL/Models/MDMA/MDMA.cpp @@ -20,7 +20,7 @@ uint8_t MDMA::get_instance_id(MDMA_Channel_TypeDef* channel) { void MDMA::prepare_transfer(Instance& instance, MDMA_LinkNodeTypeDef* first_node) { if (instance.handle.State == HAL_MDMA_STATE_BUSY) { - ErrorHandler("MDMA transfer already in progress"); + PANIC("MDMA transfer already in progress"); return; } instance_free_map[instance.id] = false; @@ -51,7 +51,7 @@ void MDMA::prepare_transfer(Instance& instance, MDMA_LinkNodeTypeDef* first_node if (HAL_MDMA_GenerateSWRequest(&instance.handle) != HAL_OK) { instance.handle.State = HAL_MDMA_STATE_BUSY; - ErrorHandler("Error generating MDMA SW request"); + PANIC("Error generating MDMA SW request"); return; } } @@ -71,7 +71,7 @@ void MDMA::inscribe(Instance& instance, uint8_t id) { MDMA_HandleTypeDef mdma_handle{}; MDMA_Channel_TypeDef* channel = get_channel(id); if (channel == nullptr) { - ErrorHandler("MDMA channel mapping not found"); + PANIC("MDMA channel mapping not found"); return; } mdma_handle.Instance = channel; @@ -114,7 +114,7 @@ void MDMA::inscribe(Instance& instance, uint8_t id) { const HAL_StatusTypeDef status = HAL_MDMA_LinkedList_CreateNode(transfer_node, &nodeConfig); if (status != HAL_OK) { - ErrorHandler("Error creating linked list in MDMA"); + PANIC("Error creating linked list in MDMA"); } instance_free_map[id] = true; @@ -130,11 +130,11 @@ void MDMA::start() { id++; if (instance.handle.Instance == nullptr) { - ErrorHandler("MDMA instance not initialised"); + PANIC("MDMA instance not initialised"); } const HAL_StatusTypeDef status = HAL_MDMA_Init(&instance.handle); if (status != HAL_OK) { - ErrorHandler("Error initialising MDMA instance"); + PANIC("Error initialising MDMA instance"); } HAL_MDMA_RegisterCallback( @@ -184,7 +184,7 @@ void MDMA::irq_handler() { void MDMA::transfer_list(MDMA::LinkedListNode* first_node, volatile bool* done) { if (transfer_queue.size() >= TRANSFER_QUEUE_MAX_SIZE) { - ErrorHandler("MDMA transfer queue full"); + PANIC("MDMA transfer queue full"); return; } transfer_queue.push({first_node, done}); @@ -218,7 +218,7 @@ void MDMA::transfer_data( void MDMA::TransferCompleteCallback(MDMA_HandleTypeDef* hmdma) { uint8_t id = get_instance_id(hmdma->Instance); if (id >= instances.size()) { - ErrorHandler("MDMA channel not registered"); + PANIC("MDMA channel not registered"); return; } @@ -234,7 +234,7 @@ void MDMA::TransferCompleteCallback(MDMA_HandleTypeDef* hmdma) { void MDMA::TransferErrorCallback(MDMA_HandleTypeDef* hmdma) { uint8_t id = get_instance_id(hmdma->Instance); if (id >= instances.size()) { - ErrorHandler("MDMA channel not registered"); + PANIC("MDMA channel not registered"); return; } @@ -244,7 +244,7 @@ void MDMA::TransferErrorCallback(MDMA_HandleTypeDef* hmdma) { } const unsigned long error_code = static_cast(hmdma->ErrorCode); - ErrorHandler("MDMA Transfer Error, code: " + std::to_string(error_code)); + PANIC("MDMA Transfer Error, code: %lu", error_code); } extern "C" void MDMA_IRQHandler(void) { MDMA::irq_handler(); } diff --git a/Src/HALAL/Models/PinModel/Pin.cpp b/Src/HALAL/Models/PinModel/Pin.cpp index 6beb2a566..a635a945c 100644 --- a/Src/HALAL/Models/PinModel/Pin.cpp +++ b/Src/HALAL/Models/PinModel/Pin.cpp @@ -62,7 +62,7 @@ const string Pin::to_string() const { void Pin::inscribe(Pin& pin, OperationMode mode) { if (pin.mode != OperationMode::NOT_USED) { - ErrorHandler( + PANIC( "Pin %s is already registered, cannot register twice", pin.to_string().c_str() ); diff --git a/Src/HALAL/Models/SPI/SPI2.cpp b/Src/HALAL/Models/SPI/SPI2.cpp index 570571c98..1f4a48c3f 100644 --- a/Src/HALAL/Models/SPI/SPI2.cpp +++ b/Src/HALAL/Models/SPI/SPI2.cpp @@ -6,7 +6,7 @@ uint32_t ST_LIB::SPIDomain::calculate_prescaler(uint32_t src_freq, uint32_t max_ prescaler *= 2; // Prescaler doubles each step (it must be a power of 2) if (prescaler > 256) { - ErrorHandler("Cannot achieve desired baudrate, speed is too low"); + PANIC("Cannot achieve desired baudrate, speed is too low"); } } @@ -24,7 +24,7 @@ extern "C" { void SPI1_IRQHandler(void) { auto inst = ST_LIB::SPIDomain::spi_instances[0]; if (inst == nullptr) { - ErrorHandler("SPI1 IRQ Handler called but instance is null"); + PANIC("SPI1 IRQ Handler called but instance is null"); return; } HAL_SPI_IRQHandler(&inst->hspi); @@ -32,7 +32,7 @@ void SPI1_IRQHandler(void) { void SPI2_IRQHandler(void) { auto inst = ST_LIB::SPIDomain::spi_instances[1]; if (inst == nullptr) { - ErrorHandler("SPI2 IRQ Handler called but instance is null"); + PANIC("SPI2 IRQ Handler called but instance is null"); return; } HAL_SPI_IRQHandler(&inst->hspi); @@ -40,7 +40,7 @@ void SPI2_IRQHandler(void) { void SPI3_IRQHandler(void) { auto inst = ST_LIB::SPIDomain::spi_instances[2]; if (inst == nullptr) { - ErrorHandler("SPI3 IRQ Handler called but instance is null"); + PANIC("SPI3 IRQ Handler called but instance is null"); return; } HAL_SPI_IRQHandler(&inst->hspi); @@ -48,7 +48,7 @@ void SPI3_IRQHandler(void) { void SPI4_IRQHandler(void) { auto inst = ST_LIB::SPIDomain::spi_instances[3]; if (inst == nullptr) { - ErrorHandler("SPI4 IRQ Handler called but instance is null"); + PANIC("SPI4 IRQ Handler called but instance is null"); return; } HAL_SPI_IRQHandler(&inst->hspi); @@ -56,7 +56,7 @@ void SPI4_IRQHandler(void) { void SPI5_IRQHandler(void) { auto inst = ST_LIB::SPIDomain::spi_instances[4]; if (inst == nullptr) { - ErrorHandler("SPI5 IRQ Handler called but instance is null"); + PANIC("SPI5 IRQ Handler called but instance is null"); return; } HAL_SPI_IRQHandler(&inst->hspi); @@ -64,7 +64,7 @@ void SPI5_IRQHandler(void) { void SPI6_IRQHandler(void) { auto inst = ST_LIB::SPIDomain::spi_instances[5]; if (inst == nullptr) { - ErrorHandler("SPI6 IRQ Handler called but instance is null"); + PANIC("SPI6 IRQ Handler called but instance is null"); return; } HAL_SPI_IRQHandler(&inst->hspi); @@ -93,7 +93,7 @@ void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef* hspi) { } else if (spi_instances[5] != nullptr && hspi == &spi_instances[5]->hspi) { inst = spi_instances[5]; } else { - ErrorHandler("SPI IRQ Callback called but instance is null"); + PANIC("SPI IRQ Callback called but instance is null"); return; } @@ -137,12 +137,12 @@ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef* hspi) { inst = spi_instances[5]; inst_idx = 5; } else { - ErrorHandler("SPI IRQ Callback called but instance is null"); + PANIC("SPI IRQ Callback called but instance is null"); return; } if (!inst->recover()) { - ErrorHandler( + PANIC( "SPI%i failed with error number %u (recovery failed, error count: %u)", inst_idx + 1, error_code, diff --git a/Src/HALAL/Services/Communication/Ethernet/LWIP/Ethernet.cpp b/Src/HALAL/Services/Communication/Ethernet/LWIP/Ethernet.cpp index 43632b8bf..db27c80dd 100644 --- a/Src/HALAL/Services/Communication/Ethernet/LWIP/Ethernet.cpp +++ b/Src/HALAL/Services/Communication/Ethernet/LWIP/Ethernet.cpp @@ -48,11 +48,11 @@ void Ethernet::start(MAC local_mac, IPV4 local_ip, IPV4 subnet_mask, IPV4 gatewa MX_LWIP_Init(); is_running = true; } else { - ErrorHandler("Unable to start Ethernet!"); + PANIC("Unable to start Ethernet!"); } if (not is_ready) { - ErrorHandler("Ethernet is not ready"); + PANIC("Ethernet is not ready"); return; } } @@ -71,13 +71,13 @@ void Ethernet::inscribe() { Pin::inscribe(PG13, ALTERNATIVE); is_ready = true; } else { - ErrorHandler("Unable to inscribe Ethernet because is already ready!"); + PANIC("Unable to inscribe Ethernet because is already ready!"); } } void Ethernet::update() { if (not is_running) { - ErrorHandler("Ethernet is not running, check if its been inscribed"); + PANIC("Ethernet is not running, check if its been inscribed"); return; } diff --git a/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.cpp b/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.cpp index 344d88073..6ce07574e 100644 --- a/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.cpp +++ b/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/ServerSocket.cpp @@ -19,7 +19,7 @@ ServerSocket::ServerSocket() = default; ServerSocket::ServerSocket(IPV4 local_ip, uint32_t local_port) : local_ip(local_ip), local_port(local_port) { if (not Ethernet::is_running) { - ErrorHandler("Cannot declare TCP server socket before Ethernet::start()"); + PANIC("Cannot declare TCP server socket before Ethernet::start()"); return; } tx_packet_buffer = {}; @@ -29,7 +29,7 @@ ServerSocket::ServerSocket(IPV4 local_ip, uint32_t local_port) state = INACTIVE; server_control_block = tcp_new(); if (server_control_block == nullptr) { - ErrorHandler("Cannot allocate TCP server control block"); + PANIC("Cannot allocate TCP server control block"); return; } tcp_nagle_disable(server_control_block); @@ -39,7 +39,7 @@ ServerSocket::ServerSocket(IPV4 local_ip, uint32_t local_port) if (error == ERR_OK) { server_control_block = tcp_listen(server_control_block); if (server_control_block == nullptr) { - ErrorHandler("Cannot switch TCP server socket into LISTEN mode"); + PANIC("Cannot switch TCP server socket into LISTEN mode"); return; } state = LISTENING; @@ -49,7 +49,7 @@ ServerSocket::ServerSocket(IPV4 local_ip, uint32_t local_port) } else { tcp_abort(server_control_block); server_control_block = nullptr; - ErrorHandler("Cannot bind server socket, error %d", (int16_t)error); + PANIC("Cannot bind server socket, error %d", (int16_t)error); return; } if (std::find(OrderProtocol::sockets.begin(), OrderProtocol::sockets.end(), this) == diff --git a/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.cpp b/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.cpp index 8adb232c7..4afcc3b97 100644 --- a/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.cpp +++ b/Src/HALAL/Services/Communication/Ethernet/LWIP/TCP/Socket.cpp @@ -122,7 +122,7 @@ Socket::Socket( : local_ip(local_ip), local_port(local_port), remote_ip(remote_ip), remote_port(remote_port), use_keep_alives{use_keep_alive} { if (not Ethernet::is_running) { - ErrorHandler("Cannot declare TCP socket before Ethernet::start()"); + PANIC("Cannot declare TCP socket before Ethernet::start()"); return; } state = INACTIVE; @@ -134,7 +134,7 @@ Socket::Socket( connection_control_block = tcp_new(); if (connection_control_block == nullptr) { - ErrorHandler("Cannot allocate TCP control block"); + PANIC("Cannot allocate TCP control block"); return; } ip_set_option(connection_control_block, SOF_REUSEADDR); @@ -143,7 +143,7 @@ Socket::Socket( if (bind_error != ERR_OK) { tcp_abort(connection_control_block); connection_control_block = nullptr; - ErrorHandler("Cannot bind TCP socket. Error code: %d", bind_error); + PANIC("Cannot bind TCP socket. Error code: %d", bind_error); return; } tcp_nagle_disable(connection_control_block); @@ -158,7 +158,7 @@ Socket::Socket( connecting_sockets.erase(remote_node); tcp_abort(connection_control_block); connection_control_block = nullptr; - ErrorHandler("Cannot connect TCP socket. Error code: %d", connect_error); + PANIC("Cannot connect TCP socket. Error code: %d", connect_error); return; } diff --git a/Src/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.cpp b/Src/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.cpp index a433d4807..8216e963b 100644 --- a/Src/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.cpp +++ b/Src/HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.cpp @@ -28,12 +28,12 @@ DatagramSocket::DatagramSocket( ) : local_ip(local_ip), local_port(local_port), remote_ip(remote_ip), remote_port(remote_port) { if (not Ethernet::is_running) { - ErrorHandler("Cannot declare UDP socket before Ethernet::start()"); + PANIC("Cannot declare UDP socket before Ethernet::start()"); return; } udp_control_block = udp_new(); if (udp_control_block == nullptr) { - ErrorHandler("Cannot allocate UDP control block"); + PANIC("Cannot allocate UDP control block"); return; } err_t error = udp_bind(udp_control_block, &local_ip.address, local_port); @@ -47,7 +47,7 @@ DatagramSocket::DatagramSocket( udp_remove(udp_control_block); udp_control_block = nullptr; is_disconnected = true; - ErrorHandler("Error binding UDP socket"); + PANIC("Error binding UDP socket"); } } @@ -106,7 +106,7 @@ void DatagramSocket::reconnect() { udp_remove(udp_control_block); udp_control_block = nullptr; is_disconnected = true; - ErrorHandler("Error binding UDP socket"); + PANIC("Error binding UDP socket"); } } diff --git a/Src/HALAL/Services/Communication/FDCAN/FDCAN.cpp b/Src/HALAL/Services/Communication/FDCAN/FDCAN.cpp index d351bc7fe..a310c83a4 100644 --- a/Src/HALAL/Services/Communication/FDCAN/FDCAN.cpp +++ b/Src/HALAL/Services/Communication/FDCAN/FDCAN.cpp @@ -45,12 +45,12 @@ void FDCAN::start() { instance->tx_data = vector(); if (HAL_FDCAN_Start(instance->hfdcan) != HAL_OK) { - ErrorHandler("Error during FDCAN %d initialization.", instance->fdcan_number); + PANIC("Error during FDCAN %d initialization.", instance->fdcan_number); } if (HAL_FDCAN_ActivateNotification(instance->hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK) { - ErrorHandler("Error activating FDCAN %d notifications.", instance->fdcan_number); + PANIC("Error activating FDCAN %d notifications.", instance->fdcan_number); } instance->start = true; @@ -62,14 +62,14 @@ void FDCAN::start() { bool FDCAN::transmit(uint8_t id, uint32_t message_id, const char* data, FDCAN::DLC dlc) { if (not FDCAN::registered_fdcan.contains(id)) { - ErrorHandler("There is no registered FDCAN with id: %d.", id); + PANIC("There is no registered FDCAN with id: %d.", id); return false; } FDCAN::Instance* instance = registered_fdcan[id]; if (not instance->start) { - ErrorHandler("The FDCAN %d is not initialized.", instance->fdcan_number); + PANIC("The FDCAN %d is not initialized.", instance->fdcan_number); return false; } @@ -83,7 +83,7 @@ bool FDCAN::transmit(uint8_t id, uint32_t message_id, const char* data, FDCAN::D HAL_FDCAN_AddMessageToTxFifoQ(instance->hfdcan, &instance->tx_header, (uint8_t*)data); if (error != HAL_OK) { - ErrorHandler( + PANIC( "Error sending message with id: 0x%x by FDCAN %d", message_id, instance->fdcan_number @@ -98,7 +98,7 @@ void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef* hfdcan, uint32_t RxFifo0ITs) bool FDCAN::read(uint8_t id, FDCAN::Packet* data) { if (not FDCAN::registered_fdcan.contains(id)) { - ErrorHandler("There is no FDCAN registered with id: %d.", id); + PANIC("There is no FDCAN registered with id: %d.", id); return false; } @@ -113,7 +113,7 @@ bool FDCAN::read(uint8_t id, FDCAN::Packet* data) { data->rx_data.data() ); if (data->identifier == FDCAN::ID::FAULT_ID) { - ErrorHandler("FAULT PROPAGATED via CAN"); + PANIC("FAULT PROPAGATED via CAN"); } data->identifier = header_buffer.Identifier; data->data_length = static_cast(header_buffer.DataLength); @@ -123,7 +123,7 @@ bool FDCAN::read(uint8_t id, FDCAN::Packet* data) { bool FDCAN::received_test(uint8_t id) { if (not FDCAN::registered_fdcan.contains(id)) { - ErrorHandler("FDCAN with id %u not registered", id); + PANIC("FDCAN with id %u not registered", id); return false; } @@ -135,7 +135,7 @@ void FDCAN::init(FDCAN::Instance* fdcan) { handle_to_id[handle] = instance_to_id[fdcan]; if (HAL_FDCAN_Init(handle) != HAL_OK) { - ErrorHandler("Error during FDCAN %d init.", fdcan->fdcan_number); + PANIC("Error during FDCAN %d init.", fdcan->fdcan_number); } } #endif diff --git a/Src/HALAL/Services/Communication/I2C/I2C.cpp b/Src/HALAL/Services/Communication/I2C/I2C.cpp index 90d3987d1..24723074f 100644 --- a/Src/HALAL/Services/Communication/I2C/I2C.cpp +++ b/Src/HALAL/Services/Communication/I2C/I2C.cpp @@ -14,7 +14,7 @@ uint16_t I2C::id_counter = 0; uint8_t I2C::inscribe(I2C::Peripheral& i2c, uint8_t address) { if (!I2C::available_i2cs.contains(i2c)) { - ErrorHandler("The I2C %d is not available on the runes files", (uint16_t)i2c); + PANIC("The I2C %d is not available on the runes files", (uint16_t)i2c); return 0; } @@ -44,14 +44,14 @@ void I2C::start() { bool I2C::transmit_next_packet(uint8_t id, I2CPacket& packet) { if (!I2C::active_i2c.contains(id)) { - ErrorHandler("I2C id not found on transmit packet \n\r"); + PANIC("I2C id not found on transmit packet \n\r"); return false; } I2C::Instance* i2c = I2C::active_i2c[id]; if (i2c->hi2c->State == HAL_I2C_STATE_BUSY_TX) { - ErrorHandler("I2C Transmit buffer busy!\n\r"); + PANIC("I2C Transmit buffer busy!\n\r"); return false; } @@ -64,7 +64,7 @@ bool I2C::transmit_next_packet(uint8_t id, I2CPacket& packet) { packet.get_data(), packet.get_size() ) != HAL_OK) { - ErrorHandler("I2C Error during memory read DMA!\n\r"); + PANIC("I2C Error during memory read DMA!\n\r"); return false; } return true; @@ -72,14 +72,14 @@ bool I2C::transmit_next_packet(uint8_t id, I2CPacket& packet) { bool I2C::transmit_next_packet_polling(uint8_t id, I2CPacket& packet) { if (!I2C::active_i2c.contains(id)) { - ErrorHandler("I2C id not found on transmit packet \n\r"); + PANIC("I2C id not found on transmit packet \n\r"); return false; } I2C::Instance* i2c = I2C::active_i2c[id]; if (i2c->hi2c->State == HAL_I2C_STATE_BUSY_TX) { - ErrorHandler("I2C Transmit buffer busy!\n\r"); + PANIC("I2C Transmit buffer busy!\n\r"); return false; } @@ -90,7 +90,7 @@ bool I2C::transmit_next_packet_polling(uint8_t id, I2CPacket& packet) { packet.get_size(), 50 ) != HAL_OK) { - // ErrorHandler("Error during I2C transmission \n\r"); + // PANIC("Error during I2C transmission \n\r"); return false; } return true; @@ -98,14 +98,14 @@ bool I2C::transmit_next_packet_polling(uint8_t id, I2CPacket& packet) { bool I2C::receive_next_packet(uint8_t id, I2CPacket& packet) { if (!I2C::active_i2c.contains(id)) { - ErrorHandler("I2C id not found on receive packet \n\r"); + PANIC("I2C id not found on receive packet \n\r"); return false; } I2C::Instance* i2c = I2C::active_i2c[id]; if (i2c->hi2c->State == HAL_I2C_STATE_BUSY_RX) { - ErrorHandler("I2C Receive buffer busy!\n\r"); + PANIC("I2C Receive buffer busy!\n\r"); return false; } @@ -117,7 +117,7 @@ bool I2C::receive_next_packet(uint8_t id, I2CPacket& packet) { packet.get_data(), packet.get_size() ) != HAL_OK) { - ErrorHandler("I2C Error during memory write DMA!\n\r"); + PANIC("I2C Error during memory write DMA!\n\r"); return false; } @@ -127,14 +127,14 @@ bool I2C::receive_next_packet(uint8_t id, I2CPacket& packet) { bool I2C::receive_next_packet_polling(uint8_t id, I2CPacket& packet) { if (!I2C::active_i2c.contains(id)) { - ErrorHandler("I2C id not found on receive packet \n\r"); + PANIC("I2C id not found on receive packet \n\r"); return false; } I2C::Instance* i2c = I2C::active_i2c[id]; if (i2c->hi2c->State == HAL_I2C_STATE_BUSY_RX) { - ErrorHandler("I2C Receive buffer busy!\n\r"); + PANIC("I2C Receive buffer busy!\n\r"); return false; } @@ -147,7 +147,7 @@ bool I2C::receive_next_packet_polling(uint8_t id, I2CPacket& packet) { packet.get_size(), 50 ) != HAL_OK) { - // ErrorHandler("I2C Error during receive!\n\r"); + // PANIC("I2C Error during receive!\n\r"); return false; } @@ -169,7 +169,7 @@ void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef* hi2c) { bool I2C::read_from(uint8_t id, I2CPacket& packet, uint16_t mem_addr, uint16_t mem_size) { if (!I2C::active_i2c.contains(id)) { - ErrorHandler("I2C id not found on read \n\r"); + PANIC("I2C id not found on read \n\r"); return false; } @@ -178,7 +178,7 @@ bool I2C::read_from(uint8_t id, I2CPacket& packet, uint16_t mem_addr, uint16_t m *packet.get_data() = 0; if (i2c->hi2c->State == HAL_I2C_STATE_BUSY_TX) { - ErrorHandler("I2C Transmit buffer busy!\n\r"); + PANIC("I2C Transmit buffer busy!\n\r"); return false; } @@ -190,7 +190,7 @@ bool I2C::read_from(uint8_t id, I2CPacket& packet, uint16_t mem_addr, uint16_t m packet.get_data(), packet.get_size() )) { - ErrorHandler("I2C Error during memory read DMA!\n\r"); + PANIC("I2C Error during memory read DMA!\n\r"); return false; } @@ -200,14 +200,14 @@ bool I2C::read_from(uint8_t id, I2CPacket& packet, uint16_t mem_addr, uint16_t m bool I2C::write_to(uint8_t id, I2CPacket& packet, uint16_t mem_addr, uint16_t mem_size) { if (!I2C::active_i2c.contains(id)) { - ErrorHandler("I2C id not found on write \n\r"); + PANIC("I2C id not found on write \n\r"); return false; } I2C::Instance* i2c = I2C::active_i2c[id]; if (i2c->hi2c->State == HAL_I2C_STATE_BUSY_RX) { - ErrorHandler("I2C Receive buffer busy!\n\r"); + PANIC("I2C Receive buffer busy!\n\r"); return false; } @@ -219,7 +219,7 @@ bool I2C::write_to(uint8_t id, I2CPacket& packet, uint16_t mem_addr, uint16_t me packet.get_data(), packet.get_size() )) { - ErrorHandler("I2C Error during memory write DMA!\n\r"); + PANIC("I2C Error during memory write DMA!\n\r"); return false; } @@ -228,7 +228,7 @@ bool I2C::write_to(uint8_t id, I2CPacket& packet, uint16_t mem_addr, uint16_t me bool I2C::has_next_packet(uint8_t id) { if (!I2C::active_i2c.contains(id)) { - ErrorHandler("I2C id not found on next packet check \n\r"); + PANIC("I2C id not found on next packet check \n\r"); return false; } @@ -239,7 +239,7 @@ bool I2C::has_next_packet(uint8_t id) { bool I2C::is_busy(uint8_t id) { if (!I2C::active_i2c.contains(id)) { - ErrorHandler("I2C id not found on is busy check \n\r"); + PANIC("I2C id not found on is busy check \n\r"); return false; } I2C::Instance* i2c = I2C::active_i2c[id]; @@ -267,7 +267,7 @@ void I2C::init(I2C::Instance* i2c) { return; } if (!available_speed_frequencies.contains(i2c->speed_frequency_kHz)) { - ErrorHandler("Error initializing, the frequency of the I2C is not available"); + PANIC("Error initializing, the frequency of the I2C is not available"); return; } i2c->hi2c->Instance = i2c->instance; @@ -281,15 +281,15 @@ void I2C::init(I2C::Instance* i2c) { i2c->hi2c->Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(i2c->hi2c) != HAL_OK) { - ErrorHandler("Error configurating I2C"); + PANIC("Error configurating I2C"); } if (HAL_I2CEx_ConfigAnalogFilter(i2c->hi2c, I2C_ANALOGFILTER_ENABLE) != HAL_OK) { - ErrorHandler("Error configurating Analog Filter of the I2C"); + PANIC("Error configurating Analog Filter of the I2C"); } if (HAL_I2CEx_ConfigDigitalFilter(i2c->hi2c, 0) != HAL_OK) { - ErrorHandler("Error configurating Digital Filter of the I2C"); + PANIC("Error configurating Digital Filter of the I2C"); } i2c->is_initialized = true; } diff --git a/Src/HALAL/Services/Communication/SPI/SPI.cpp b/Src/HALAL/Services/Communication/SPI/SPI.cpp index ed71c54e5..4abf8c1a8 100644 --- a/Src/HALAL/Services/Communication/SPI/SPI.cpp +++ b/Src/HALAL/Services/Communication/SPI/SPI.cpp @@ -23,7 +23,7 @@ uint16_t SPI::id_counter = 0; uint8_t SPI::inscribe(SPI::Peripheral& spi) { if (!SPI::available_spi.contains(spi)) { - ErrorHandler(" The SPI peripheral %d is already used or does not exists.", (uint16_t)spi); + PANIC(" The SPI peripheral %d is already used or does not exists.", (uint16_t)spi); return 0; } @@ -64,7 +64,7 @@ uint8_t SPI::inscribe(SPI::Peripheral& spi) { void SPI::assign_RS(uint8_t id, Pin& RSPin) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return; } @@ -100,7 +100,7 @@ bool SPI::transmit(uint8_t id, uint8_t data) { bool SPI::transmit(uint8_t id, span data) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return false; } @@ -116,7 +116,7 @@ bool SPI::transmit(uint8_t id, span data) { return false; break; default: - ErrorHandler( + PANIC( "Error while transmiting and receiving with spi DMA. Errorcode " "%u", (uint8_t)errorcode @@ -128,7 +128,7 @@ bool SPI::transmit(uint8_t id, span data) { bool SPI::transmit_DMA(uint8_t id, span data) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return false; } @@ -144,7 +144,7 @@ bool SPI::transmit_DMA(uint8_t id, span data) { return false; break; default: - ErrorHandler( + PANIC( "Error while transmiting and receiving with spi DMA. Errorcode " "%u", (uint8_t)errorcode @@ -156,7 +156,7 @@ bool SPI::transmit_DMA(uint8_t id, span data) { bool SPI::receive(uint8_t id, span data) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return false; } @@ -172,7 +172,7 @@ bool SPI::receive(uint8_t id, span data) { return false; break; default: - ErrorHandler( + PANIC( "Error while transmiting and receiving with spi DMA. Errorcode " "%u", (uint8_t)errorcode @@ -183,7 +183,7 @@ bool SPI::receive(uint8_t id, span data) { } bool SPI::receive_DMA(uint8_t id, span data) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return false; } @@ -198,7 +198,7 @@ bool SPI::receive_DMA(uint8_t id, span data) { return false; break; default: - ErrorHandler( + PANIC( "Error while transmiting and receiving with spi DMA. Errorcode " "%u", (uint8_t)errorcode @@ -210,7 +210,7 @@ bool SPI::receive_DMA(uint8_t id, span data) { bool SPI::transmit_and_receive(uint8_t id, span command_data, span receive_data) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return false; } @@ -223,7 +223,7 @@ bool SPI::transmit_and_receive(uint8_t id, span command_data, spanhspi, receive_data.data(), receive_data.size(), 10) != HAL_OK) { turn_on_chip_select(spi); - ErrorHandler("Error during receive in %s", spi->name.c_str()); + PANIC("Error during receive in %s", spi->name.c_str()); return false; } turn_on_chip_select(spi); @@ -235,7 +235,7 @@ bool SPI::transmit_and_receive_DMA( span receive_data ) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return false; } @@ -255,7 +255,7 @@ bool SPI::transmit_and_receive_DMA( return false; break; default: - ErrorHandler( + PANIC( "Error while transmiting and receiving with spi DMA. Errorcode " "%u", (uint8_t)errorcode @@ -271,7 +271,7 @@ bool SPI::transmit_and_receive_DMA( bool SPI::master_transmit_Order(uint8_t id, SPIBaseOrder& Order) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return false; } @@ -289,7 +289,7 @@ bool SPI::master_transmit_Order(uint8_t id, SPIBaseOrder& Order) { bool SPI::master_transmit_Order(uint8_t id, SPIBaseOrder* Order) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return false; } @@ -307,7 +307,7 @@ bool SPI::master_transmit_Order(uint8_t id, SPIBaseOrder* Order) { void SPI::slave_listen_Orders(uint8_t id) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return; } @@ -372,7 +372,7 @@ void SPI::slave_check_packet_ID(SPI::Instance* spi) { void SPI::chip_select_on(uint8_t id) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return; } @@ -382,7 +382,7 @@ void SPI::chip_select_on(uint8_t id) { void SPI::chip_select_off(uint8_t id) { if (!SPI::registered_spi.contains(id)) { - ErrorHandler("No SPI registered with id %u", id); + PANIC("No SPI registered with id %u", id); return; } @@ -425,7 +425,7 @@ void SPI::init(SPI::Instance* spi) { spi->hspi->Init.IOSwap = SPI_IO_SWAP_DISABLE; if (HAL_SPI_Init(spi->hspi) != HAL_OK) { - ErrorHandler("Unable to init %s", spi->name); + PANIC("Unable to init %s", spi->name); return; } @@ -434,7 +434,7 @@ void SPI::init(SPI::Instance* spi) { // void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef* hspi) { // if (!SPI::registered_spi_by_handler.contains(hspi)) { -// ErrorHandler("Used SPI protocol without the HALAL SPI interface"); +// PANIC("Used SPI protocol without the HALAL SPI interface"); // return; // } @@ -472,7 +472,7 @@ void SPI::init(SPI::Instance* spi) { // } // } // } else { -// ErrorHandler("Used master transmit Order on a slave spi"); +// PANIC("Used master transmit Order on a slave spi"); // } // break; // } @@ -490,7 +490,7 @@ void SPI::init(SPI::Instance* spi) { // SPI::mark_slave_ready(spi); // spi->state = SPI::PROCESSING_ORDER; // } else { -// ErrorHandler("Used slave process Orders on a master spi"); +// PANIC("Used slave process Orders on a master spi"); // } // break; // } @@ -540,7 +540,7 @@ void SPI::init(SPI::Instance* spi) { // break; // } // default: -// ErrorHandler("Unknown spi state: %d", spi->state); +// PANIC("Unknown spi state: %d", spi->state); // break; // } // } @@ -553,7 +553,7 @@ void SPI::init(SPI::Instance* spi) { // // if ((hspi->ErrorCode & HAL_SPI_ERROR_UDR) != 0) { // // SPI::spi_recover(SPI::registered_spi_by_handler[hspi], hspi); // // } else { -// // ErrorHandler("SPI error number %u", hspi->ErrorCode); +// // PANIC("SPI error number %u", hspi->ErrorCode); // // } // // } diff --git a/Src/HALAL/Services/Communication/UART/UART.cpp b/Src/HALAL/Services/Communication/UART/UART.cpp index c820b8758..7772626f1 100644 --- a/Src/HALAL/Services/Communication/UART/UART.cpp +++ b/Src/HALAL/Services/Communication/UART/UART.cpp @@ -14,7 +14,7 @@ uint16_t UART::id_counter = 0; uint8_t UART::inscribe(UART::Peripheral& uart) { if (!UART::available_uarts.contains(uart)) { - ErrorHandler(" The UART peripheral %d is already used or does not exists.", (uint16_t)uart); + PANIC(" The UART peripheral %d is already used or does not exists.", (uint16_t)uart); return 0; } diff --git a/Src/HALAL/Services/DigitalInputService/DigitalInputService.cpp b/Src/HALAL/Services/DigitalInputService/DigitalInputService.cpp index b003ddbd7..aa6e07f39 100644 --- a/Src/HALAL/Services/DigitalInputService/DigitalInputService.cpp +++ b/Src/HALAL/Services/DigitalInputService/DigitalInputService.cpp @@ -18,7 +18,7 @@ uint8_t DigitalInput::inscribe(Pin& pin) { PinState DigitalInput::read_pin_state(uint8_t id) { if (not DigitalInput::service_ids.contains(id)) { - ErrorHandler("ID %d is not registered as a DigitalInput", id); + PANIC("ID %d is not registered as a DigitalInput", id); return PinState::OFF; } diff --git a/Src/HALAL/Services/DigitalOutputService/DigitalOutputService.cpp b/Src/HALAL/Services/DigitalOutputService/DigitalOutputService.cpp index 889ecf865..04175769f 100644 --- a/Src/HALAL/Services/DigitalOutputService/DigitalOutputService.cpp +++ b/Src/HALAL/Services/DigitalOutputService/DigitalOutputService.cpp @@ -20,7 +20,7 @@ uint8_t DigitalOutputService::inscribe(Pin& pin) { void DigitalOutputService::turn_off(uint8_t id) { if (not DigitalOutputService::service_ids.contains(id)) { - ErrorHandler("ID %d is not registered as a DigitalOutput", id); + PANIC("ID %d is not registered as a DigitalOutput", id); return; } @@ -30,7 +30,7 @@ void DigitalOutputService::turn_off(uint8_t id) { void DigitalOutputService::turn_on(uint8_t id) { if (not DigitalOutputService::service_ids.contains(id)) { - ErrorHandler("ID %d is not registered as a DigitalOutput", id); + PANIC("ID %d is not registered as a DigitalOutput", id); return; } @@ -40,7 +40,7 @@ void DigitalOutputService::turn_on(uint8_t id) { void DigitalOutputService::set_pin_state(uint8_t id, PinState state) { if (not DigitalOutputService::service_ids.contains(id)) { - ErrorHandler("ID %d is not registered as a DigitalOutput", id); + PANIC("ID %d is not registered as a DigitalOutput", id); return; } @@ -50,7 +50,7 @@ void DigitalOutputService::set_pin_state(uint8_t id, PinState state) { void DigitalOutputService::toggle(uint8_t id) { if (not DigitalOutputService::service_ids.contains(id)) { - ErrorHandler("ID %d is not registered as a DigitalOutput", id); + PANIC("ID %d is not registered as a DigitalOutput", id); return; } @@ -60,7 +60,7 @@ void DigitalOutputService::toggle(uint8_t id) { bool DigitalOutputService::lock_pin_state(uint8_t id, PinState state) { if (not DigitalOutputService::service_ids.contains(id)) { - ErrorHandler("ID %d is not registered as a DigitalOutput", id); + PANIC("ID %d is not registered as a DigitalOutput", id); return false; } diff --git a/Src/HALAL/Services/FMAC/FMAC.cpp b/Src/HALAL/Services/FMAC/FMAC.cpp index f72c61af5..c78ff22f5 100644 --- a/Src/HALAL/Services/FMAC/FMAC.cpp +++ b/Src/HALAL/Services/FMAC/FMAC.cpp @@ -17,7 +17,7 @@ void MultiplierAccelerator::IIR_software_in_software_out_inscribe( int16_t* feedback_coefficient_array ) { if (input_coefficient_array_size > 63 || feedback_coefficient_array_size > 63) { - ErrorHandler("Error while configurating IIR FMAC, no coefficient can be greater than 63"); + PANIC("Error while configurating IIR FMAC, no coefficient can be greater than 63"); return; } @@ -46,7 +46,7 @@ void MultiplierAccelerator::start() { if (Instance.mode != MultiplierAccelerator::None) { Instance.hfmac->Instance = FMAC; if (HAL_FMAC_Init(Instance.hfmac) != HAL_OK) { - ErrorHandler("Error while initialising the FMAC"); + PANIC("Error while initialising the FMAC"); } FMAC_FilterConfigTypeDef sFmacConfig; @@ -76,7 +76,7 @@ void MultiplierAccelerator::start() { sFmacConfig.R = 0; if (HAL_FMAC_FilterConfig(Instance.hfmac, &sFmacConfig) != HAL_OK) { - ErrorHandler("Error while configurating the FMAC"); + PANIC("Error while configurating the FMAC"); } } else { @@ -93,7 +93,7 @@ void MultiplierAccelerator::software_preload( #if FMAC_ERROR_CHECK != 0 if (amount_to_preload > MemoryLayout.FInSize || amount_to_preload > MemoryLayout.FeedbackInSize) { - ErrorHandler( + PANIC( "Error while preloading data, cannot preload more data than the structure can hold" ); } @@ -109,7 +109,7 @@ void MultiplierAccelerator::software_preload( preload_feedback_data, amount_to_feedback_preload ) != HAL_OK) { - ErrorHandler("Unexpected error on preload of data"); + PANIC("Unexpected error on preload of data"); } } @@ -118,7 +118,7 @@ void MultiplierAccelerator::software_run(int16_t* calculated_data, uint16_t* out Process.output_data_size = *output_size; Process.state = MultiplierAccelerator::WAITING_DATA; if (HAL_FMAC_FilterStart(Instance.hfmac, calculated_data, output_size) != HAL_OK) { - ErrorHandler("Error while starting FMAC"); + PANIC("Error while starting FMAC"); } } @@ -126,7 +126,7 @@ void MultiplierAccelerator::software_load_input(int16_t* input_data, uint16_t* i Process.input_data = input_data; Process.input_data_size = *input_size; if (HAL_FMAC_AppendFilterData(Instance.hfmac, input_data, input_size) != HAL_OK) { - ErrorHandler("Error while loading data into the FMAC"); + PANIC("Error while loading data into the FMAC"); } Process.state = MultiplierAccelerator::RUNNING; } @@ -140,13 +140,13 @@ void MultiplierAccelerator::software_load_repeat(int16_t* input_data, uint16_t* Process.running_output_data, &Process.output_data_size ) != HAL_OK) { - ErrorHandler("Error while preparing buffer for the FMAC"); + PANIC("Error while preparing buffer for the FMAC"); } Process.input_data = input_data; Process.input_data_size = *input_size; if (HAL_FMAC_AppendFilterData(Instance.hfmac, input_data, input_size) != HAL_OK) { - ErrorHandler("Error while loading data into the FMAC"); + PANIC("Error while loading data into the FMAC"); } } @@ -161,19 +161,19 @@ void MultiplierAccelerator::software_load_full( Process.running_output_data = output_data; Process.output_data_size = *output_size; if (HAL_FMAC_ConfigFilterOutputBuffer(Instance.hfmac, output_data, output_size) != HAL_OK) { - ErrorHandler("Error while preparing buffer for the FMAC"); + PANIC("Error while preparing buffer for the FMAC"); } Process.input_data = input_data; Process.input_data_size = *input_size; if (HAL_FMAC_AppendFilterData(Instance.hfmac, input_data, input_size) != HAL_OK) { - ErrorHandler("Error while loading data into the FMAC"); + PANIC("Error while loading data into the FMAC"); } } void MultiplierAccelerator::software_stop() { if (HAL_FMAC_FilterStop(Instance.hfmac) != HAL_OK) { - ErrorHandler("Error while stopping FMAC"); + PANIC("Error while stopping FMAC"); } } @@ -193,4 +193,4 @@ void HAL_FMAC_OutputDataReadyCallback(FMAC_HandleTypeDef* hfmac) { MultiplierAccelerator::Process.state = MultiplierAccelerator::WAITING_DATA; } -void HAL_FMAC_ErrorCallback(FMAC_HandleTypeDef* hfmac) { ErrorHandler("Error while running FMAC"); } +void HAL_FMAC_ErrorCallback(FMAC_HandleTypeDef* hfmac) { PANIC("Error while running FMAC"); } diff --git a/Src/HALAL/Services/Flash/Flash.cpp b/Src/HALAL/Services/Flash/Flash.cpp index ee6edde97..7e55002d7 100644 --- a/Src/HALAL/Services/Flash/Flash.cpp +++ b/Src/HALAL/Services/Flash/Flash.cpp @@ -9,7 +9,7 @@ void Flash::read(uint32_t source_addr, uint32_t* result, uint32_t number_of_words) { if (source_addr < FLASH_START_ADDRESS || source_addr > FLASH_END_ADDRESS) { - ErrorHandler("Address out of memory when trying to read flash memory."); + PANIC("Address out of memory when trying to read flash memory."); return; } @@ -26,7 +26,7 @@ void Flash::read(uint32_t source_addr, uint32_t* result, uint32_t number_of_word // TODO: Estaria muy bien optimizar el uso de ram en la escritura de múltiples sectores bool Flash::write(uint32_t* source, uint32_t dest_addr, uint32_t number_of_words) { if (dest_addr < FLASH_SECTOR0_START_ADDRESS || dest_addr > FLASH_END_ADDRESS) { - ErrorHandler("Address out of memory when trying to write flash memory."); + PANIC("Address out of memory when trying to write flash memory."); return false; } diff --git a/Src/HALAL/Services/Time/RTC.cpp b/Src/HALAL/Services/Time/RTC.cpp index 13e027559..bade78507 100644 --- a/Src/HALAL/Services/Time/RTC.cpp +++ b/Src/HALAL/Services/Time/RTC.cpp @@ -32,7 +32,7 @@ void Global_RTC::start_rtc() { if (HAL_RTC_Init(&hrtc) != HAL_OK) { rtc_start_in_progress = false; - ErrorHandler("Error on RTC Init"); + PANIC("Error on RTC Init"); return; } sTime.Hours = 0x0; @@ -43,7 +43,7 @@ void Global_RTC::start_rtc() { if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) { rtc_start_in_progress = false; - ErrorHandler("Error while setting time at RTC start"); + PANIC("Error while setting time at RTC start"); return; } @@ -54,7 +54,7 @@ void Global_RTC::start_rtc() { if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) { rtc_start_in_progress = false; - ErrorHandler("Error while setting date at RTC start"); + PANIC("Error while setting date at RTC start"); return; } @@ -70,6 +70,8 @@ bool Global_RTC::ensure_started() { return rtc_started; } +bool Global_RTC::is_started() { return rtc_started; } + bool Global_RTC::has_valid_time() { return rtc_time_valid; } RTCData Global_RTC::get_rtc_timestamp() { @@ -116,11 +118,11 @@ void Global_RTC::set_rtc_data( bool write_ok = true; if (HAL_RTC_SetTime(&hrtc, &gTime, RTC_FORMAT_BIN) != HAL_OK) { write_ok = false; - ErrorHandler("Error on writing Time on the RTC"); + PANIC("Error on writing Time on the RTC"); } if (HAL_RTC_SetDate(&hrtc, &gDate, RTC_FORMAT_BIN) != HAL_OK) { write_ok = false; - ErrorHandler("Error on writing Date on the RTC"); + PANIC("Error on writing Date on the RTC"); } rtc_time_valid = write_ok; if (write_ok) { @@ -143,6 +145,8 @@ void Global_RTC::update_rtc_data() { void Global_RTC::start_rtc() {} +bool Global_RTC::is_started() { return false; } + bool Global_RTC::ensure_started() { return false; } bool Global_RTC::has_valid_time() { return false; } diff --git a/Src/HALAL/Services/Time/Scheduler.cpp b/Src/HALAL/Services/Time/Scheduler.cpp index ac215b4b4..6d90a9c4e 100644 --- a/Src/HALAL/Services/Time/Scheduler.cpp +++ b/Src/HALAL/Services/Time/Scheduler.cpp @@ -58,10 +58,13 @@ void Scheduler_start(void) { ST_LIB::TimerDomain::callbacks[ST_LIB::timer_idxmap[static_cast(SCHEDULER_TIMER_DOMAIN )]] = Scheduler_global_timer_callback; - uint16_t prescaler = - (uint16_t)(ST_LIB::TimerDomain::get_timer_frequency(Scheduler_global_timer) / - Scheduler::FREQUENCY); - Scheduler_global_timer->PSC = prescaler; + uint32_t prescaler_divisor = + ST_LIB::TimerDomain::get_timer_frequency(Scheduler_global_timer) / Scheduler::FREQUENCY; + if (prescaler_divisor == 0 || prescaler_divisor > (uint32_t)UINT16_MAX + 1u) { + PANIC("Invalid prescaler value: %u", prescaler_divisor); + return; + } + Scheduler_global_timer->PSC = static_cast(prescaler_divisor - 1u); Scheduler_global_timer->ARR = 0; Scheduler_global_timer->DIER |= LL_TIM_DIER_UIE; Scheduler_global_timer->CR1 = @@ -97,14 +100,7 @@ void Scheduler::update() { } } -uint64_t Scheduler::get_global_tick() { - SchedLock(); - uint64_t val = global_tick_us_ + Scheduler_global_timer->CNT; - SchedUnlock(); - return val; -} - -inline uint8_t Scheduler::allocate_slot() { +inline uint8_t Scheduler::allocate_slot() { uint32_t idx = __builtin_ffs(Scheduler::free_bitmap_) - 1; if (idx >= Scheduler::kMaxTasks) [[unlikely]] return static_cast(Scheduler::INVALID_ID); diff --git a/Src/ST-LIB_LOW/Sd/Sd.cpp b/Src/ST-LIB_LOW/Sd/Sd.cpp index a3769d5d3..0157d8bc1 100644 --- a/Src/ST-LIB_LOW/Sd/Sd.cpp +++ b/Src/ST-LIB_LOW/Sd/Sd.cpp @@ -23,9 +23,9 @@ void SdDomain::Instance::on_dma_write_complete() { SDMMC_CmdStopTransfer(hsd.Instance); } -void SdDomain::Instance::on_abort() { ErrorHandler("SD Card operation aborted"); } +void SdDomain::Instance::on_abort() { PANIC("SD Card operation aborted"); } -void SdDomain::Instance::on_error() { ErrorHandler("SD Card error occurred"); } +void SdDomain::Instance::on_error() { PANIC("SD Card error occurred"); } bool SdDomain::Instance::is_card_present() { return cd_instance->first->read() == cd_instance->second; @@ -300,7 +300,7 @@ void HAL_SD_AbortCallback(SD_HandleTypeDef* hsd) { if (auto sd_instance = ST_LIB::get_sd_instance(hsd)) { sd_instance->on_abort(); } else { - ErrorHandler("SD Card operation aborted"); + PANIC("SD Card operation aborted"); } } @@ -308,7 +308,7 @@ void HAL_SD_ErrorCallback(SD_HandleTypeDef* hsd) { if (auto sd_instance = ST_LIB::get_sd_instance(hsd)) { sd_instance->on_error(); } else { - ErrorHandler("SD Card error occurred"); + PANIC("SD Card error occurred"); } } From d30db85d716045747ff112370c82e12dbc6db6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 8 Apr 2026 19:30:35 +0200 Subject: [PATCH 07/23] Update build, includes, and infrastructure for PM-no-ETH refactoring - CMakeLists: remove FaultRuntime from build, add DiagnosticSinks - ST-LIB.hpp: update includes after FaultRuntime removal - StaticVector, MeanCalculator, Zeroing, StateOrder variants: minor include and compatibility fixes - README: update status notes --- CMakeLists.txt | 1 - Inc/C++Utilities/StaticVector.hpp | 2 +- Inc/ST-LIB.hpp | 65 +++++++++++++++++-- .../Control/Blocks/MeanCalculator.hpp | 2 +- Inc/ST-LIB_HIGH/Control/Blocks/Zeroing.hpp | 2 +- .../StateMachine/HeapStateOrder.hpp | 2 +- .../StateMachine/StackStateOrder.hpp | 2 +- Inc/ST-LIB_LOW/StateMachine/StateMachine.hpp | 24 +++---- Inc/ST-LIB_LOW/StateMachine/StateOrder.hpp | 4 +- README.md | 2 + 10 files changed, 82 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3162bc342..af28973e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -392,7 +392,6 @@ set(STLIB_HIGH_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashStorer.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashVariable.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/FaultController.cpp - ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp ) diff --git a/Inc/C++Utilities/StaticVector.hpp b/Inc/C++Utilities/StaticVector.hpp index 31b5a5c48..ca340a7e5 100644 --- a/Inc/C++Utilities/StaticVector.hpp +++ b/Inc/C++Utilities/StaticVector.hpp @@ -18,7 +18,7 @@ template class StaticVector { constexpr void push_back(const T& value) { if (size_ >= Capacity) { - ErrorHandler("StaticVector capacity exceeded"); + PANIC("StaticVector capacity exceeded"); return; } data[size_] = value; diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 2faf432a6..8c5067fe8 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -5,7 +5,6 @@ #include "HALAL/Services/Diagnostics/Diagnostics.hpp" #include "HALAL/HALAL.hpp" #include "ST-LIB_HIGH.hpp" -#include "ST-LIB_HIGH/Protections/FaultRuntime.hpp" #include "ST-LIB_LOW.hpp" class STLIB { @@ -31,6 +30,54 @@ class STLIB { namespace ST_LIB { extern void compile_error(const char* msg); +template struct FaultPolicy { + static_assert( + IsStateMachineClass>, + "FaultPolicy operational machine must be a StateMachine" + ); + + using fault_policy_tag = void; + static constexpr bool has_operational_machine = true; + static constexpr auto& operational_machine = OperationalMachine; + static constexpr Callback on_fault_enter = OnFaultEnter; + + template consteval std::size_t inscribe(Ctx&) const { return 0; } +}; + +template struct FaultPolicyNoMachine { + using fault_policy_tag = void; + static constexpr bool has_operational_machine = false; + static constexpr Callback on_fault_enter = OnFaultEnter; + + template consteval std::size_t inscribe(Ctx&) const { return 0; } +}; + +template struct is_fault_policy_type : std::false_type {}; + +template +struct is_fault_policy_type> : std::true_type {}; + +template +struct is_fault_policy_type> : std::true_type {}; + +template +inline constexpr bool is_fault_policy_type_v = is_fault_policy_type>::value; + +using DefaultFaultPolicy = FaultPolicyNoMachine<>; + +template struct select_fault_policy_type { + using type = DefaultPolicy; +}; + +template +struct select_fault_policy_type { + using CleanT = std::remove_cvref_t; + using type = std::conditional_t< + is_fault_policy_type_v, + CleanT, + typename select_fault_policy_type::type>; +}; + // The contract of BuildCtx/Board is documented in docs/st-lib-board-contract.md. template struct BuildCtx { template using Decl = typename D::Entry; @@ -143,6 +190,15 @@ consteval std::array build_dma_configs( } // namespace BuildUtils template struct Board { +private: + static constexpr std::size_t fault_policy_count = + ((is_fault_policy_type_v> ? 1u : 0u) + ... + 0u); + static_assert(fault_policy_count <= 1, "Board supports at most one FaultPolicy"); + + using SelectedFaultPolicy = + typename select_fault_policy_type::type; + +public: static consteval auto build_ctx() { DomainsCtx ctx{}; (devs.inscribe(ctx), ...); @@ -268,6 +324,9 @@ template struct Board { HALconfig::system_clock(); HALconfig::peripheral_clock(); + Diagnostics::Runtime::install_default_sinks(); + FaultController::template install_runtime(); + #ifdef HAL_RTC_MODULE_ENABLED (void)Global_RTC::ensure_started(); #endif @@ -309,10 +368,8 @@ template struct Board { GPIODomain::Init::instances ); - Diagnostics::Runtime::install_default_sinks(); - FaultRuntime::install_default_broadcasters(); ProtectionEngine::initialize(); - // ... + FaultController::start(); } template diff --git a/Inc/ST-LIB_HIGH/Control/Blocks/MeanCalculator.hpp b/Inc/ST-LIB_HIGH/Control/Blocks/MeanCalculator.hpp index 4ccaf9c7b..967d8dff9 100644 --- a/Inc/ST-LIB_HIGH/Control/Blocks/MeanCalculator.hpp +++ b/Inc/ST-LIB_HIGH/Control/Blocks/MeanCalculator.hpp @@ -13,7 +13,7 @@ template class MeanCalculator : public ControlBlock { mean += input_value / N; index++; if (index > N) - ErrorHandler("MeanCalculator is receiving just 0"); + PANIC("MeanCalculator is receiving just 0"); if (index == N) output_value = mean; else diff --git a/Inc/ST-LIB_HIGH/Control/Blocks/Zeroing.hpp b/Inc/ST-LIB_HIGH/Control/Blocks/Zeroing.hpp index 4d69e5d9c..6552ac634 100644 --- a/Inc/ST-LIB_HIGH/Control/Blocks/Zeroing.hpp +++ b/Inc/ST-LIB_HIGH/Control/Blocks/Zeroing.hpp @@ -19,7 +19,7 @@ template class Zeroing : public ControlBlock max_value_offset) { - ErrorHandler("Zeroing offset is calculated to be above specified maximum"); + PANIC("Zeroing offset is calculated to be above specified maximum"); } else sensor.set_offset(sensor.get_offset() - mean_calculator.output_value); mean_calculator.reset(); diff --git a/Inc/ST-LIB_LOW/StateMachine/HeapStateOrder.hpp b/Inc/ST-LIB_LOW/StateMachine/HeapStateOrder.hpp index 502ee5779..b8b76c321 100644 --- a/Inc/ST-LIB_LOW/StateMachine/HeapStateOrder.hpp +++ b/Inc/ST-LIB_LOW/StateMachine/HeapStateOrder.hpp @@ -16,7 +16,7 @@ template class HeapStateOrder : public HeapOrder { ) : HeapOrder(id, callback, values...), state_machine(state_machine), state(state) { if (not state_machine.get_states().contains(state)) { - ErrorHandler("State Machine does not contain state, cannot add StateOrder"); + PANIC("State Machine does not contain state, cannot add StateOrder"); return; } else state_machine.get_states()[static_cast(state)].add_state_order(id); diff --git a/Inc/ST-LIB_LOW/StateMachine/StackStateOrder.hpp b/Inc/ST-LIB_LOW/StateMachine/StackStateOrder.hpp index 98c6d6d55..9d0aa363b 100644 --- a/Inc/ST-LIB_LOW/StateMachine/StackStateOrder.hpp +++ b/Inc/ST-LIB_LOW/StateMachine/StackStateOrder.hpp @@ -17,7 +17,7 @@ class StackStateOrder : public StackOrder { : StackOrder(id, callback, values...), state_machine(state_machine), state(state) { if (not state_machine.get_states().contains(state)) { - ErrorHandler("State Machine does not contain state, cannot add StateOrder"); + PANIC("State Machine does not contain state, cannot add StateOrder"); return; } else state_machine.get_states()[static_cast(state)].add_state_order(id); diff --git a/Inc/ST-LIB_LOW/StateMachine/StateMachine.hpp b/Inc/ST-LIB_LOW/StateMachine/StateMachine.hpp index 4b90187ca..d74818422 100644 --- a/Inc/ST-LIB_LOW/StateMachine/StateMachine.hpp +++ b/Inc/ST-LIB_LOW/StateMachine/StateMachine.hpp @@ -71,7 +71,7 @@ template consteval State(StateEnum state, T... transitions) : state(state) { if (((transitions.target == state) || ...)) { - ErrorHandler("Current state cannot be the target of a transition"); + PANIC("Current state cannot be the target of a transition"); } (this->transitions.push_back(transitions), ...); } @@ -155,7 +155,7 @@ template (sorted_states[i].get_state()) != i) { - ErrorHandler("States Enum must be contiguous and start from 0"); + PANIC("States Enum must be contiguous and start from 0"); } } @@ -448,7 +448,7 @@ class StateMachine : public IStateMachine { void check_transitions() override { if (!called_start) [[unlikely]] { - ErrorHandler("Error: check_transitions called before StateMachine.start()"); + PANIC("Error: check_transitions called before StateMachine.start()"); return; } auto& [i, n] = transitions_assoc[static_cast(current_state)]; @@ -512,7 +512,7 @@ class StateMachine : public IStateMachine { return states[i].add_cyclic_action(action, period); } } - ErrorHandler("Error: The state is not added to the state machine"); + PANIC("Error: The state is not added to the state machine"); return nullptr; } @@ -524,7 +524,7 @@ class StateMachine : public IStateMachine { return; } } - ErrorHandler("Error: The state is not added to the state machine"); + PANIC("Error: The state is not added to the state machine"); } template @@ -535,7 +535,7 @@ class StateMachine : public IStateMachine { return; } } - ErrorHandler("Error: The state is not added to the state machine"); + PANIC("Error: The state is not added to the state machine"); } template @@ -546,7 +546,7 @@ class StateMachine : public IStateMachine { return; } } - ErrorHandler("Error: The state is not added to the state machine"); + PANIC("Error: The state is not added to the state machine"); } StateEnum get_current_state() const { return current_state; } diff --git a/Inc/ST-LIB_LOW/StateMachine/StateOrder.hpp b/Inc/ST-LIB_LOW/StateMachine/StateOrder.hpp index 3b8468d9b..e94968f10 100644 --- a/Inc/ST-LIB_LOW/StateMachine/StateOrder.hpp +++ b/Inc/ST-LIB_LOW/StateMachine/StateOrder.hpp @@ -18,7 +18,7 @@ class StateOrder : public Order { static void add_state_orders(std::span new_ids) { if (informer_socket == nullptr) { - ErrorHandler("Informer Socket has not been set"); + PANIC("Informer Socket has not been set"); return; }; state_orders_ids = new_ids; @@ -28,7 +28,7 @@ class StateOrder : public Order { static void remove_state_orders(std::span old_ids) { if (informer_socket == nullptr) { - ErrorHandler("Informer Socket has not been set"); + PANIC("Informer Socket has not been set"); return; } state_orders_ids = old_ids; diff --git a/README.md b/README.md index 7cb8d4a57..f3c31d1e1 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ ctest --preset simulator-all - Setup: [`docs/setup.md`](docs/setup.md) - Build and presets: [`docs/build-and-presets.md`](docs/build-and-presets.md) - Testing: [`docs/testing.md`](docs/testing.md) +- Protections and diagnostics: [`docs/protections-and-diagnostics.md`](docs/protections-and-diagnostics.md) +- Board contract: [`docs/st-lib-board-contract.md`](docs/st-lib-board-contract.md) - Releases: [`docs/releases.md`](docs/releases.md) ## Recommended Presets From ee51f2d1952b5ff8c1bf4558daa8717947121da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 8 Apr 2026 19:30:48 +0200 Subject: [PATCH 08/23] Add diagnostics and fault controller test coverage - DiagnosticsHub tests: history when no sinks, per-sink retry, bounded pending queue, reinstall clears latch, fault transitions once, delegation to nested operational machine, early-fault bootstrap (fault before start), source location captured in runtime reporters, invalid rule config rejected without side effects - PANIC/FAULT enter global FAULT and publish URGENT diagnostic record - WARNING/INFO publish without entering FAULT - ProtectionEngine: evaluate triggers fault and publishes protection event - TestAccess: white-box accessors for DiagnosticsHub and ProtectionEngine - Update adc_test, spi2_test, dma2_test, encoder_test, common_tests for new reporter API --- Tests/CMakeLists.txt | 2 +- Tests/TestAccess.hpp | 43 +++++++ Tests/Time/common_tests.cpp | 76 +++++------ Tests/Time/encoder_test.cpp | 10 +- Tests/adc_test.cpp | 18 +-- Tests/diagnostics_test.cpp | 245 +++++++++++++++++++++++++++--------- Tests/dma2_test.cpp | 14 +-- Tests/spi2_test.cpp | 22 ++-- 8 files changed, 290 insertions(+), 140 deletions(-) create mode 100644 Tests/TestAccess.hpp diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index dc105d47a..0a41fa5b8 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -28,7 +28,6 @@ add_executable(${STLIB_TEST_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Models/DMA/DMA2.cpp ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Services/DFSDM/DFSDM.cpp ${CMAKE_CURRENT_LIST_DIR}/../Src/ST-LIB_HIGH/Protections/FaultController.cpp - ${CMAKE_CURRENT_LIST_DIR}/../Src/ST-LIB_HIGH/Protections/FaultRuntime.cpp ${CMAKE_CURRENT_LIST_DIR}/../Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/encoder_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/scheduler_test.cpp @@ -66,6 +65,7 @@ target_compile_definitions(${STLIB_TEST_EXECUTABLE} PRIVATE ) target_include_directories(${STLIB_TEST_EXECUTABLE} PRIVATE + ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/../Inc ${CMAKE_CURRENT_LIST_DIR}/../Inc/ST-LIB_LOW ${CMAKE_CURRENT_LIST_DIR}/../Inc/ST-LIB_HIGH diff --git a/Tests/TestAccess.hpp b/Tests/TestAccess.hpp new file mode 100644 index 000000000..550408f23 --- /dev/null +++ b/Tests/TestAccess.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionEngine.hpp" + +namespace ST_LIB::TestAccess { + +struct DiagnosticsHub { + static void clear() { + for (size_t sink_index = 0; sink_index < Diagnostics::Hub::sink_count; ++sink_index) { + Diagnostics::Hub::sink_storage[sink_index].reset(); + Diagnostics::Hub::sinks[sink_index] = nullptr; + } + Diagnostics::Hub::sink_count = 0; + Diagnostics::Hub::history_count = 0; + Diagnostics::Hub::history_next_index = 0; + Diagnostics::Hub::pending_count = 0; + Diagnostics::Runtime::defaults_installed = false; + } + + static size_t history_size() { return Diagnostics::Hub::history_count; } + + static size_t pending_size() { return Diagnostics::Hub::pending_count; } +}; + +struct ProtectionEngine { + static void clear() { + for (size_t protection_index = 0; protection_index < ::ProtectionEngine::protection_count; + ++protection_index) { + if (::ProtectionEngine::protections[protection_index].has_value()) { + visit( + [](auto& protection) { protection.clear_runtime_state(); }, + *::ProtectionEngine::protections[protection_index] + ); + ::ProtectionEngine::protections[protection_index].reset(); + } + } + ::ProtectionEngine::protection_count = 0; + ::ProtectionEngine::registration_locked = false; + } +}; + +} // namespace ST_LIB::TestAccess diff --git a/Tests/Time/common_tests.cpp b/Tests/Time/common_tests.cpp index 6e34db21d..0c741093c 100644 --- a/Tests/Time/common_tests.cpp +++ b/Tests/Time/common_tests.cpp @@ -1,14 +1,13 @@ #include + +#include + #include "HALAL/Services/Diagnostics/Diagnostics.hpp" #include "ST-LIB_HIGH/Protections/FaultController.hpp" #include "ErrorHandler/ErrorHandler.hpp" #include "HALAL/Services/InfoWarning/InfoWarning.hpp" -int ErrorHandlerModel::line = 0; -const char* ErrorHandlerModel::func = ""; -const char* ErrorHandlerModel::file = ""; - -namespace ST_LIB::TestErrorHandler { +namespace ST_LIB::TestPanicReporter { bool fail_on_error = true; int call_count = 0; @@ -18,63 +17,44 @@ void reset() { } void set_fail_on_error(bool enabled) { fail_on_error = enabled; } -} // namespace ST_LIB::TestErrorHandler +} // namespace ST_LIB::TestPanicReporter -void ErrorHandlerModel::SetMetaData(int line, const char* func, const char* file) { - ErrorHandlerModel::line = line; - ErrorHandlerModel::func = func; - ErrorHandlerModel::file = file; -} - -void ErrorHandlerModel::ErrorHandlerTrigger(const char* format, ...) { +void PanicReporter::Trigger(const std::source_location& location, const char* format, ...) { char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; va_list arguments; va_start(arguments, format); const int32_t written = vsnprintf(buffer, sizeof(buffer), format, arguments); va_end(arguments); - ST_LIB::TestErrorHandler::call_count++; - Diagnostics::Hub::publish_runtime_error( + ST_LIB::TestPanicReporter::call_count++; + FaultController::request_fault(FaultCause::panic( buffer, written < 0 || static_cast(written) >= sizeof(buffer), - line, - func, - file - ); - FaultController::enter_fault(); - if (ST_LIB::TestErrorHandler::fail_on_error) { + static_cast(location.line()), + location.function_name(), + location.file_name() + )); + if (ST_LIB::TestPanicReporter::fail_on_error) { EXPECT_EQ(1, 0); } } -void ErrorHandlerModel::ErrorHandlerUpdate() {} - -std::string InfoWarning::line; -std::string InfoWarning::func; -std::string InfoWarning::file; - -namespace ST_LIB::TestInfoWarning { -bool fail_on_error = false; -int call_count = 0; - -void reset() { - fail_on_error = false; - call_count = 0; -} +void PanicReporter::Flush() {} -void set_fail_on_error(bool enabled) { fail_on_error = enabled; } -}; // namespace ST_LIB::TestInfoWarning +void FaultReporter::Trigger(const std::source_location& location, const char* format, ...) { + char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; + va_list arguments; + va_start(arguments, format); + const int32_t written = vsnprintf(buffer, sizeof(buffer), format, arguments); + va_end(arguments); -void InfoWarning::SetMetaData(int line, const char* func, const char* file) { - InfoWarning::line = to_string(line); - InfoWarning::func = string(func); - InfoWarning::file = string(file); + FaultController::request_fault(FaultCause::runtime_fault( + buffer, + written < 0 || static_cast(written) >= sizeof(buffer), + static_cast(location.line()), + location.function_name(), + location.file_name() + )); } -void InfoWarning::InfoWarningTrigger(string format, ...) { - (void)format; - ST_LIB::TestInfoWarning::call_count++; - if (ST_LIB::TestInfoWarning::fail_on_error) { - EXPECT_EQ(1, 0); - } -} +void FaultReporter::Flush() {} diff --git a/Tests/Time/encoder_test.cpp b/Tests/Time/encoder_test.cpp index 057e05e4e..7152cc48a 100644 --- a/Tests/Time/encoder_test.cpp +++ b/Tests/Time/encoder_test.cpp @@ -3,11 +3,11 @@ #include "HALAL/Services/Time/TimerWrapper.hpp" #include "ST-LIB_LOW/Sensors/EncoderSensor/NewEncoderSensor.hpp" -namespace ST_LIB::TestErrorHandler { +namespace ST_LIB::TestPanicReporter { void reset(); void set_fail_on_error(bool enabled); extern int call_count; -} // namespace ST_LIB::TestErrorHandler +} // namespace ST_LIB::TestPanicReporter namespace { @@ -56,7 +56,7 @@ class EncoderTest : public ::testing::Test { ST_LIB::TimerWrapper wrapper{}; void SetUp() override { - ST_LIB::TestErrorHandler::reset(); + ST_LIB::TestPanicReporter::reset(); TIM2_BASE->CNT = 0U; TIM2_BASE->ARR = 0U; @@ -87,7 +87,7 @@ TEST_F(EncoderTest, ResetUsesConfiguredInitialCounterValue) { } TEST_F(EncoderTest, TurnOffKeepsTryingIfHALStopFails) { - ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::TestPanicReporter::set_fail_on_error(false); ST_LIB::Encoder::turn_on(); instance.hal_tim = nullptr; @@ -95,7 +95,7 @@ TEST_F(EncoderTest, TurnOffKeepsTryingIfHALStopFails) { ST_LIB::Encoder::turn_off(); ST_LIB::Encoder::turn_off(); - EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 2); + EXPECT_EQ(ST_LIB::TestPanicReporter::call_count, 2); } TEST(EncoderSensorTest, ReadTreatsEncoderInitialCounterAsZeroPosition) { diff --git a/Tests/adc_test.cpp b/Tests/adc_test.cpp index 6003512b3..88b113c4f 100644 --- a/Tests/adc_test.cpp +++ b/Tests/adc_test.cpp @@ -8,11 +8,11 @@ #include "MockedDrivers/mocked_hal_adc.hpp" #include "MockedDrivers/mocked_hal_dma.hpp" -namespace ST_LIB::TestErrorHandler { +namespace ST_LIB::TestPanicReporter { void reset(); void set_fail_on_error(bool enabled); extern int call_count; -} // namespace ST_LIB::TestErrorHandler +} // namespace ST_LIB::TestPanicReporter namespace { @@ -322,7 +322,7 @@ class ADCTest : public ::testing::Test { void reset_runtime_state() { ST_LIB::MockedHAL::adc_reset(); ST_LIB::MockedHAL::dma_reset(); - ST_LIB::TestErrorHandler::reset(); + ST_LIB::TestPanicReporter::reset(); clear_nvic_enables(); clear_dma_irq_table(); } @@ -545,7 +545,7 @@ TEST_F(ADCTest, Resolution10BitDMAClampsRawBufferToResolutionRange) { } TEST_F(ADCTest, InitWithoutDMAInstancesFailsInsteadOfConfiguringDMAAtRuntime) { - ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::TestPanicReporter::set_fail_on_error(false); float output = -1.0f; const std::array cfgs{{ @@ -562,14 +562,14 @@ TEST_F(ADCTest, InitWithoutDMAInstancesFailsInsteadOfConfiguringDMAAtRuntime) { SingleADCInit::init(cfgs); - EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_EQ(ST_LIB::TestPanicReporter::call_count, 1); EXPECT_EQ(hadc1.DMA_Handle, nullptr); EXPECT_FALSE(ST_LIB::MockedHAL::adc_is_dma_running(ADC1)); EXPECT_EQ(ST_LIB::MockedHAL::adc_get_call_count(ST_LIB::MockedHAL::ADCOperation::StartDMA), 0U); } TEST_F(ADCTest, DMAStartFailureTriggersErrorPathAndLeavesInstanceUnreadable) { - ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::TestPanicReporter::set_fail_on_error(false); ST_LIB::MockedHAL::dma_set_start_status(HAL_ERROR); float output = -1.0f; @@ -587,14 +587,14 @@ TEST_F(ADCTest, DMAStartFailureTriggersErrorPathAndLeavesInstanceUnreadable) { init_adc_with_dma<1, single_adc1_init_cfgs>(cfgs); - EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_EQ(ST_LIB::TestPanicReporter::call_count, 1); EXPECT_FALSE(ST_LIB::MockedHAL::adc_is_dma_running(ADC1)); EXPECT_EQ(SingleADCInit::instances[0].handle, nullptr); EXPECT_FLOAT_EQ(SingleADCInit::instances[0].get_raw(), 0.0f); } TEST_F(ADCTest, UnresolvedConfigDoesNotAliasAResolvedPeripheralInstance) { - ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::TestPanicReporter::set_fail_on_error(false); float unresolved = -1.0f; float resolved = -1.0f; @@ -621,7 +621,7 @@ TEST_F(ADCTest, UnresolvedConfigDoesNotAliasAResolvedPeripheralInstance) { init_adc_with_dma<2, shared_adc1_init_cfgs>(cfgs); - EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_EQ(ST_LIB::TestPanicReporter::call_count, 1); EXPECT_EQ(SharedADCInit::instances[0].handle, nullptr); EXPECT_EQ(SharedADCInit::instances[0].dma_slot, nullptr); EXPECT_EQ(SharedADCInit::instances[1].handle, &hadc1); diff --git a/Tests/diagnostics_test.cpp b/Tests/diagnostics_test.cpp index 272ab7a10..a0d10ed0c 100644 --- a/Tests/diagnostics_test.cpp +++ b/Tests/diagnostics_test.cpp @@ -1,20 +1,24 @@ #include +#include "ErrorHandler/ErrorHandler.hpp" #include "HALAL/Services/Diagnostics/Diagnostics.hpp" #include "HALAL/Services/InfoWarning/InfoWarning.hpp" #include "ST-LIB_HIGH/Protections/FaultController.hpp" #include "ST-LIB_HIGH/Protections/ProtectionEngine.hpp" #include "ST-LIB_HIGH/Protections/Rules.hpp" #include "ST-LIB_HIGH/Protections/SampleSource.hpp" -#include "ErrorHandler/ErrorHandler.hpp" +#include "StateMachine/StateMachine.hpp" +#include "TestAccess.hpp" -namespace ST_LIB::TestErrorHandler { +namespace ST_LIB::TestPanicReporter { void set_fail_on_error(bool enabled); +void reset(); } namespace { -namespace TestErrorHandler = ST_LIB::TestErrorHandler; +namespace TestAccess = ST_LIB::TestAccess; +namespace TestPanicReporter = ST_LIB::TestPanicReporter; static_assert(Protections::ReadableSampleSource>); static_assert(!Protections::ReadableSampleSource); @@ -42,42 +46,62 @@ class RecordingSink final : public Diagnostics::DiagnosticSink { vector records{}; }; -class DummyStateMachine final : public IStateMachine { -public: - void check_transitions() override {} - - void force_change_state(size_t state) override { - force_change_calls++; - current_state_id = state; - } +enum class OperationalState : uint8_t { RUN = 0, HOLD = 1 }; + +bool transition_to_hold = false; +size_t fault_enter_calls = 0; +size_t operational_hold_enter_count = 0; + +static constexpr auto operational_run_state = + make_state(OperationalState::RUN, Transition{OperationalState::HOLD, []() { + return transition_to_hold; + }}); +static constexpr auto operational_hold_state = make_state(OperationalState::HOLD); + +static inline auto test_operational_machine = []() consteval { + auto sm = make_state_machine(OperationalState::RUN, operational_run_state, operational_hold_state); + sm.add_enter_action([]() { operational_hold_enter_count++; }, operational_hold_state); + return sm; +}(); + +void reset_operational_machine() { + transition_to_hold = false; + operational_hold_enter_count = 0; + test_operational_machine.force_change_state(static_cast(OperationalState::RUN)); + test_operational_machine.get_states()[0].unregister_all_timed_actions(); + test_operational_machine.get_states()[1].unregister_all_timed_actions(); +} - size_t get_current_state_id() const override { return current_state_id; } +void on_fault_enter() { fault_enter_calls++; } - size_t force_change_calls{0}; - size_t current_state_id{0}; +uint32_t emit_warning_and_return_line() { + constexpr uint32_t expected_line = __LINE__ + 1; + WARNING("source location warning"); + return expected_line; +} -protected: - void enter() override {} - void exit() override {} - void start() override {} +struct NoMachinePolicy { + static constexpr bool has_operational_machine = false; + static constexpr Callback on_fault_enter = &::on_fault_enter; }; -class CountingBroadcaster final : public FaultBroadcaster { -public: - bool broadcast_fault() override { - calls++; - return true; - } - - size_t calls{0}; +struct OperationalPolicy { + static constexpr bool has_operational_machine = true; + static constexpr auto& operational_machine = test_operational_machine; + static constexpr Callback on_fault_enter = &::on_fault_enter; }; class DiagnosticsHubTest : public ::testing::Test { protected: void SetUp() override { - Diagnostics::Hub::clear_for_testing(); - ProtectionEngine::clear_for_testing(); - FaultController::clear_broadcasters_for_testing(); + TestAccess::DiagnosticsHub::clear(); + TestAccess::ProtectionEngine::clear(); + reset_operational_machine(); + TestPanicReporter::reset(); + fault_enter_calls = 0; + + FaultController::install_runtime(); + FaultController::start(); } }; @@ -89,8 +113,8 @@ TEST_F(DiagnosticsHubTest, KeepsLocalHistoryWhenNoSinksAreRegistered) { snprintf(record.payload.runtime.message, sizeof(record.payload.runtime.message), "local only"); Diagnostics::Hub::publish(record); - EXPECT_EQ(Diagnostics::Hub::history_size_for_testing(), 1u); - EXPECT_EQ(Diagnostics::Hub::pending_size_for_testing(), 0u); + EXPECT_EQ(TestAccess::DiagnosticsHub::history_size(), 1u); + EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), 0u); } TEST_F(DiagnosticsHubTest, RetriesOnlyTheSinkThatFailed) { @@ -111,12 +135,12 @@ TEST_F(DiagnosticsHubTest, RetriesOnlyTheSinkThatFailed) { Diagnostics::Hub::flush(); EXPECT_EQ(stable_sink->publish_calls, 1u); EXPECT_EQ(flaky_sink->publish_calls, 1u); - EXPECT_EQ(Diagnostics::Hub::pending_size_for_testing(), 1u); + EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), 1u); Diagnostics::Hub::flush(); EXPECT_EQ(stable_sink->publish_calls, 1u); EXPECT_EQ(flaky_sink->publish_calls, 2u); - EXPECT_EQ(Diagnostics::Hub::pending_size_for_testing(), 0u); + EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), 0u); } TEST_F(DiagnosticsHubTest, PendingQueueIsBoundedWhenASinkNeverDelivers) { @@ -130,38 +154,92 @@ TEST_F(DiagnosticsHubTest, PendingQueueIsBoundedWhenASinkNeverDelivers) { record.severity = Diagnostics::Severity::WARNING; record.category = Diagnostics::Category::RUNTIME_WARNING; snprintf(record.origin, sizeof(record.origin), "test"); - snprintf(record.payload.runtime.message, sizeof(record.payload.runtime.message), "event %zu", record_index); + snprintf( + record.payload.runtime.message, + sizeof(record.payload.runtime.message), + "event %zu", + record_index + ); Diagnostics::Hub::publish(record); } - EXPECT_EQ(Diagnostics::Hub::pending_size_for_testing(), Diagnostics::Config::pending_capacity); + EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), Diagnostics::Config::pending_capacity); +} + +TEST_F(DiagnosticsHubTest, ReinstallingRuntimeClearsLatchedFaultState) { + FaultController::request_fault(FaultCause::external("test", "first fault")); + ASSERT_TRUE(FaultController::is_faulted()); + ASSERT_NE(FaultController::latched_fault_cause(), nullptr); + + FaultController::install_runtime(); + FaultController::start(); + + EXPECT_FALSE(FaultController::is_faulted()); + EXPECT_EQ(FaultController::latched_fault_cause(), nullptr); + EXPECT_EQ(fault_enter_calls, 1u); } TEST_F(DiagnosticsHubTest, FaultControllerTransitionsOnlyOnce) { - DummyStateMachine machine{}; + FaultController::request_fault(FaultCause::external("test", "fault once")); + FaultController::request_fault(FaultCause::external("test", "fault twice")); + + ASSERT_TRUE(FaultController::is_faulted()); + ASSERT_NE(FaultController::latched_fault_cause(), nullptr); + EXPECT_EQ(fault_enter_calls, 1u); + EXPECT_STREQ(FaultController::latched_fault_cause()->origin, "test"); +} - FaultController::link_state_machine(machine, 4); - auto broadcaster_result = FaultController::emplace_broadcaster(); - ASSERT_TRUE(broadcaster_result.has_value()); - auto* broadcaster = *broadcaster_result; +TEST_F(DiagnosticsHubTest, FaultControllerDelegatesToOperationalMachineWhileOperational) { + FaultController::install_runtime(); + reset_operational_machine(); + FaultController::start(); - FaultController::enter_fault(); - FaultController::enter_fault(); + transition_to_hold = true; + FaultController::check_transitions(); - EXPECT_EQ(machine.current_state_id, 4u); - EXPECT_EQ(machine.force_change_calls, 1u); - EXPECT_EQ(broadcaster->calls, 1u); + EXPECT_EQ(test_operational_machine.get_current_state(), OperationalState::HOLD); + EXPECT_EQ(operational_hold_enter_count, 1u); +} + +TEST_F(DiagnosticsHubTest, FaultBeforeStartStartsRuntimeDirectlyInFault) { + FaultController::install_runtime(); + reset_operational_machine(); + + FaultController::request_fault(FaultCause::external("test", "fault before start")); + + EXPECT_TRUE(FaultController::is_faulted()); + EXPECT_EQ(fault_enter_calls, 0u); + + FaultController::start(); + transition_to_hold = true; + FaultController::check_transitions(); + + EXPECT_EQ(fault_enter_calls, 1u); + EXPECT_EQ(test_operational_machine.get_current_state(), OperationalState::RUN); + EXPECT_EQ(operational_hold_enter_count, 0u); +} + +TEST_F(DiagnosticsHubTest, FaultControllerStopsDelegatingAfterFault) { + FaultController::install_runtime(); + reset_operational_machine(); + FaultController::start(); + + FaultController::request_fault(FaultCause::external("test", "stop delegating")); + transition_to_hold = true; + FaultController::check_transitions(); + + EXPECT_EQ(test_operational_machine.get_current_state(), OperationalState::RUN); + EXPECT_EQ(fault_enter_calls, 1u); } TEST_F(DiagnosticsHubTest, ProtectionEngineEvaluatesRulesAndPublishesSnapshots) { - DummyStateMachine machine{}; float monitored_value = 2.0f; SampleSource source(monitored_value); auto sink_result = Diagnostics::Hub::emplace_sink(); ASSERT_TRUE(sink_result.has_value()); auto* sink = *sink_result; - FaultController::link_state_machine(machine, 7); + auto protection = ProtectionEngine::create_protection("monitored_value", source); ASSERT_TRUE(protection.has_value()); ASSERT_TRUE(protection->add_rule(Protections::Rules::below(1.0f, 1.5f)).has_value()); @@ -172,8 +250,10 @@ TEST_F(DiagnosticsHubTest, ProtectionEngineEvaluatesRulesAndPublishesSnapshots) Diagnostics::Hub::flush(); ASSERT_FALSE(sink->records.empty()); - EXPECT_EQ(machine.current_state_id, 7u); + EXPECT_TRUE(FaultController::is_faulted()); EXPECT_EQ(sink->records.front().category, Diagnostics::Category::PROTECTION_EVENT); + EXPECT_EQ(sink->records.front().severity, Diagnostics::Severity::FAULT); + EXPECT_EQ(sink->records.front().priority, Diagnostics::DiagnosticPriority::URGENT); EXPECT_EQ( sink->records.front().payload.protection.state, Protections::RuleState::FAULT @@ -184,39 +264,86 @@ TEST_F(DiagnosticsHubTest, ProtectionEngineEvaluatesRulesAndPublishesSnapshots) ); } -TEST_F(DiagnosticsHubTest, ErrorHandlerPublishesAndEntersFault) { - DummyStateMachine machine{}; +TEST_F(DiagnosticsHubTest, PanicPublishesAndEntersFault) { + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + TestPanicReporter::set_fail_on_error(false); + + PANIC("runtime panic %d", 12); + Diagnostics::Hub::flush(); + ASSERT_FALSE(sink->records.empty()); + EXPECT_TRUE(FaultController::is_faulted()); + EXPECT_EQ(fault_enter_calls, 1u); + EXPECT_EQ(sink->records.front().category, Diagnostics::Category::RUNTIME_PANIC); + EXPECT_EQ(sink->records.front().severity, Diagnostics::Severity::FAULT); + EXPECT_EQ(sink->records.front().priority, Diagnostics::DiagnosticPriority::URGENT); +} + +TEST_F(DiagnosticsHubTest, FaultPublishesAndEntersFault) { auto sink_result = Diagnostics::Hub::emplace_sink(); ASSERT_TRUE(sink_result.has_value()); auto* sink = *sink_result; - FaultController::link_state_machine(machine, 5); - TestErrorHandler::set_fail_on_error(false); - ErrorHandler("runtime failure %d", 12); + FAULT("runtime fault %d", 12); Diagnostics::Hub::flush(); ASSERT_FALSE(sink->records.empty()); - EXPECT_EQ(machine.current_state_id, 5u); - EXPECT_EQ(sink->records.front().category, Diagnostics::Category::RUNTIME_ERROR); + EXPECT_TRUE(FaultController::is_faulted()); + EXPECT_EQ(fault_enter_calls, 1u); + EXPECT_EQ(sink->records.front().category, Diagnostics::Category::RUNTIME_FAULT); EXPECT_EQ(sink->records.front().severity, Diagnostics::Severity::FAULT); + EXPECT_EQ(sink->records.front().priority, Diagnostics::DiagnosticPriority::URGENT); } TEST_F(DiagnosticsHubTest, WarningDoesNotEnterFault) { - DummyStateMachine machine{}; - auto sink_result = Diagnostics::Hub::emplace_sink(); ASSERT_TRUE(sink_result.has_value()); auto* sink = *sink_result; - FaultController::link_state_machine(machine, 9); WARNING("runtime warning %d", 3); Diagnostics::Hub::flush(); ASSERT_FALSE(sink->records.empty()); - EXPECT_EQ(machine.current_state_id, 0u); + EXPECT_FALSE(FaultController::is_faulted()); EXPECT_EQ(sink->records.front().category, Diagnostics::Category::RUNTIME_WARNING); EXPECT_EQ(sink->records.front().severity, Diagnostics::Severity::WARNING); + EXPECT_EQ(sink->records.front().priority, Diagnostics::DiagnosticPriority::NORMAL); +} + +TEST_F(DiagnosticsHubTest, InfoPublishesWithoutEnteringFault) { + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + + INFO("runtime info %d", 7); + Diagnostics::Hub::flush(); + + ASSERT_FALSE(sink->records.empty()); + EXPECT_FALSE(FaultController::is_faulted()); + EXPECT_EQ(sink->records.front().category, Diagnostics::Category::RUNTIME_INFO); + EXPECT_EQ(sink->records.front().severity, Diagnostics::Severity::INFO); + EXPECT_EQ(sink->records.front().priority, Diagnostics::DiagnosticPriority::NORMAL); +} + +TEST_F(DiagnosticsHubTest, RuntimeDiagnosticsCaptureCallerSourceLocation) { + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + + const uint32_t expected_line = emit_warning_and_return_line(); + Diagnostics::Hub::flush(); + + ASSERT_FALSE(sink->records.empty()); + EXPECT_EQ(sink->records.front().payload.runtime.line, expected_line); + EXPECT_NE( + strstr( + sink->records.front().payload.runtime.function_name, + "emit_warning_and_return_line" + ), + nullptr + ); } TEST_F(DiagnosticsHubTest, RejectsInvalidRuleConfigurationsWithoutGlobalSideEffects) { diff --git a/Tests/dma2_test.cpp b/Tests/dma2_test.cpp index f7906f209..3c695cfb2 100644 --- a/Tests/dma2_test.cpp +++ b/Tests/dma2_test.cpp @@ -7,11 +7,11 @@ #include "MockedDrivers/NVIC.hpp" #include "MockedDrivers/mocked_hal_dma.hpp" -namespace ST_LIB::TestErrorHandler { +namespace ST_LIB::TestPanicReporter { void reset(); void set_fail_on_error(bool enabled); extern int call_count; -} // namespace ST_LIB::TestErrorHandler +} // namespace ST_LIB::TestPanicReporter extern "C" { void DMA1_Stream0_IRQHandler(void); @@ -234,7 +234,7 @@ class DMA2Test : public ::testing::Test { protected: void SetUp() override { ST_LIB::MockedHAL::dma_reset(); - ST_LIB::TestErrorHandler::reset(); + ST_LIB::TestPanicReporter::reset(); clear_nvic_enables(); clear_dma_irq_table(); } @@ -305,13 +305,13 @@ TEST_F(DMA2Test, ScheduledTransferTimingSerializesSharedBusUsage) { } TEST_F(DMA2Test, InitFailureTriggersErrorAndSkipsRegistration) { - ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::TestPanicReporter::set_fail_on_error(false); ST_LIB::MockedHAL::dma_set_init_status(HAL_ERROR); ST_LIB::DMADomain::Init<2>::init(spi_dma_cfg); EXPECT_EQ(ST_LIB::MockedHAL::dma_get_call_count(ST_LIB::MockedHAL::DMAOperation::Init), 2U); - EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 2); + EXPECT_EQ(ST_LIB::TestPanicReporter::call_count, 2); EXPECT_EQ(dma_irq_table[0], nullptr); EXPECT_EQ(dma_irq_table[1], nullptr); } @@ -353,7 +353,7 @@ TEST_F(DMA2Test, InitUsesPrecomputedDMAInitDataWithoutRuntimeReconfiguration) { } TEST_F(DMA2Test, InitRejectsCorruptedConfigWithNoAssignedStream) { - ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::TestPanicReporter::set_fail_on_error(false); auto corrupted_cfg = spi_dma_cfg; std::get<2>(corrupted_cfg[0].init_data) = ST_LIB::DMADomain::Stream::none; @@ -361,7 +361,7 @@ TEST_F(DMA2Test, InitRejectsCorruptedConfigWithNoAssignedStream) { ST_LIB::DMADomain::Init<2>::init(corrupted_cfg); - EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_EQ(ST_LIB::TestPanicReporter::call_count, 1); EXPECT_EQ(ST_LIB::MockedHAL::dma_get_call_count(ST_LIB::MockedHAL::DMAOperation::Init), 1U); EXPECT_EQ(dma_irq_table[0], nullptr); EXPECT_EQ(dma_irq_table[1], &ST_LIB::DMADomain::Init<2>::instances[1].dma); diff --git a/Tests/spi2_test.cpp b/Tests/spi2_test.cpp index ad081703f..1c44348e7 100644 --- a/Tests/spi2_test.cpp +++ b/Tests/spi2_test.cpp @@ -9,11 +9,11 @@ #include "MockedDrivers/mocked_hal_spi.hpp" extern uint32_t SystemCoreClock; -namespace ST_LIB::TestErrorHandler { +namespace ST_LIB::TestPanicReporter { void reset(); void set_fail_on_error(bool enabled); extern int call_count; -} // namespace ST_LIB::TestErrorHandler +} // namespace ST_LIB::TestPanicReporter namespace { @@ -96,7 +96,7 @@ class SPI2Test : public ::testing::Test { SystemCoreClock = 64'000'000U; ST_LIB::MockedHAL::spi_reset(); ST_LIB::MockedHAL::dma_reset(); - ST_LIB::TestErrorHandler::reset(); + ST_LIB::TestPanicReporter::reset(); clear_nvic_enables(); clear_dma_irq_table(); for (auto& inst : ST_LIB::SPIDomain::spi_instances) { @@ -204,14 +204,14 @@ TEST_F(SPI2Test, InitWith32BitDataUsesWordAlignmentAndPrescaler) { } TEST_F(SPI2Test, InitFailureOnHALSPIInitDoesNotRegisterOrEnableNVIC) { - ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::TestPanicReporter::set_fail_on_error(false); ST_LIB::MockedHAL::spi_set_status(HAL_ERROR); init_spi( 20'000'000U ); - EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_EQ(ST_LIB::TestPanicReporter::call_count, 1); EXPECT_EQ(ST_LIB::SPIDomain::spi_instances[1], nullptr); EXPECT_EQ(NVIC_GetEnableIRQ(SPI2_IRQn), 0U); } @@ -292,15 +292,15 @@ TEST_F(SPI2Test, DMACompletionCallbacksSetOperationFlag) { } TEST_F(SPI2Test, CallbacksWithUnknownHandleTriggerErrorPath) { - ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::TestPanicReporter::set_fail_on_error(false); SPI_HandleTypeDef unknown{}; HAL_SPI_TxCpltCallback(&unknown); - EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_EQ(ST_LIB::TestPanicReporter::call_count, 1); } TEST_F(SPI2Test, ErrorCallbackOnKnownHandleRecoversWithoutErrorPath) { - ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::TestPanicReporter::set_fail_on_error(false); auto& instance = init_spi( 20'000'000U @@ -317,7 +317,7 @@ TEST_F(SPI2Test, ErrorCallbackOnKnownHandleRecoversWithoutErrorPath) { hspi->ErrorCode = 0x55U; HAL_SPI_ErrorCallback(hspi); - EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 0); + EXPECT_EQ(ST_LIB::TestPanicReporter::call_count, 0); EXPECT_EQ( ST_LIB::MockedHAL::spi_get_call_count(ST_LIB::MockedHAL::SPIOperation::Abort), before_abort + 1U @@ -331,7 +331,7 @@ TEST_F(SPI2Test, ErrorCallbackOnKnownHandleRecoversWithoutErrorPath) { } TEST_F(SPI2Test, ErrorCallbackOnKnownHandleTriggersErrorPathWhenRecoveryFails) { - ST_LIB::TestErrorHandler::set_fail_on_error(false); + ST_LIB::TestPanicReporter::set_fail_on_error(false); auto& instance = init_spi( 20'000'000U @@ -344,7 +344,7 @@ TEST_F(SPI2Test, ErrorCallbackOnKnownHandleTriggersErrorPathWhenRecoveryFails) { ST_LIB::MockedHAL::spi_set_status(HAL_ERROR); HAL_SPI_ErrorCallback(hspi); - EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1); + EXPECT_EQ(ST_LIB::TestPanicReporter::call_count, 1); EXPECT_EQ(spi.get_error_count(), 1U); EXPECT_TRUE(spi.was_aborted()); } From 17a78623a74d669782b5babb08f55e745109c5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 8 Apr 2026 19:30:56 +0200 Subject: [PATCH 09/23] Add protections-and-diagnostics architecture guide Documents the current model: FaultController ownership, FaultPolicy nesting, PANIC/FAULT/WARNING/INFO semantics, early-fault bootstrap, FaultCause vs DiagnosticRecord separation, Diagnostics Hub memory policy, and C++23 design choices. --- docs/protections-and-diagnostics.md | 438 ++++++++++++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100644 docs/protections-and-diagnostics.md diff --git a/docs/protections-and-diagnostics.md b/docs/protections-and-diagnostics.md new file mode 100644 index 000000000..85ddf6a07 --- /dev/null +++ b/docs/protections-and-diagnostics.md @@ -0,0 +1,438 @@ +# Protections, Faults, and Diagnostics + +This document describes the current protection, fault, diagnostic, and transmission model in +`ST-LIB`. + +It is intentionally split into two parts: + +- **How to use** +- **Internal development** + +If you only want to integrate protections into an application, read the first part only. + +## 1. How to Use + +### 1.1 Mental Model + +The subsystem has three explicit runtime operations: + +- `Board::init()` +- `ProtectionEngine::evaluate()` +- `Diagnostics::Hub::flush()` + +If the application uses an operational state machine nested under the global runtime, it also +polls: + +- `FaultController::check_transitions()` + +The global fault model is always the same: + +- the framework owns a global runtime with two states: `OPERATIONAL` and `FAULT` +- the only valid way to enter the global `FAULT` state is `FaultController::request_fault(...)` +- protection faults, `PANIC(...)`, and `FAULT(...)` all end up there +- fault diagnostics are transmitted with urgent priority through `Diagnostics` + +```mermaid +flowchart TD + A["Register protections"] --> B["Declare Board and optional FaultPolicy"] + B --> C["Board::init()"] + C --> D["while (1)"] + D --> E["FaultController::check_transitions()"] + D --> F["ProtectionEngine::evaluate()"] + D --> G["Diagnostics::Hub::flush()"] +``` + +### 1.2 Registering Protections + +Protections are created through `ProtectionEngine::create_protection(...)`. + +Each protection: + +- has a stable name +- reads from one `SampleSource` +- owns one or more rules + +Rules are added through the factories in `Protections::Rules`. + +Available rule factories: + +- `Rules::below(...)` +- `Rules::above(...)` +- `Rules::range(...)` +- `Rules::equals(...)` +- `Rules::not_equals(...)` +- `Rules::time_accumulation(...)` + +Both `create_protection(...)` and `add_rule(...)` return `std::expected`, so configuration errors +must be handled explicitly. + +### 1.3 When to Register Protections + +Register protections before `Board::init()`. + +The intended lifecycle is: + +1. registration +2. `Board::init()` +3. evaluation and flushing in the runtime loop + +After `Board::init()`, the protection registry is locked. + +### 1.4 Typical Protection Example + +```cpp +#include "ST-LIB.hpp" + +using namespace ST_LIB; + +constexpr auto led = DigitalOutputDomain::DigitalOutput(PF13); +using MainBoard = Board; + +float bus_voltage = 0.0f; + +int main() { + auto protection = ProtectionEngine::create_protection( + "bus_voltage", + SampleSource{bus_voltage} + ); + + if (!protection.has_value()) { + PANIC("failed to register bus_voltage protection"); + } + + if (!protection->add_rule(Protections::Rules::below(350.0f, 370.0f)).has_value()) { + PANIC("failed to add below rule"); + } + + if (!protection->add_rule( + Protections::Rules::time_accumulation(20.0f, 15.0f, 0.5f, 10000.0f) + ) + .has_value()) { + PANIC("failed to add time_accumulation rule"); + } + + MainBoard::init(); + + while (1) { + ProtectionEngine::evaluate(); + Diagnostics::Hub::flush(); + } +} +``` + +### 1.5 Global Fault Runtime + +`Board::init()` always installs and starts the global fault runtime. + +That runtime has two states: + +- `OPERATIONAL` +- `FAULT` + +If the application does not use a functional state machine, nothing else is required. + +If the application does use a functional state machine, it can be nested inside `OPERATIONAL` +through a `FaultPolicy`. + +Example: + +```cpp +enum class AppState : uint8_t { IDLE = 0, RUN = 1 }; + +static constexpr auto idle_state = make_state(AppState::IDLE); +static constexpr auto run_state = make_state(AppState::RUN); + +static inline auto app_machine = make_state_machine(AppState::IDLE, idle_state, run_state); + +static void on_fault_enter() { + // disable power stage, set LEDs, open contactors, etc. +} + +static inline constexpr FaultPolicy fault_policy{}; +using MainBoard = Board; + +int main() { + MainBoard::init(); + + while (1) { + FaultController::check_transitions(); + ProtectionEngine::evaluate(); + Diagnostics::Hub::flush(); + } +} +``` + +Important rules: + +- the user state machine models operational behavior only +- the user does not program transitions to the global `FAULT` +- if a fatal condition must force the system into `FAULT`, use `PANIC(...)`, `FAULT(...)`, or + `FaultController::request_fault(...)` +- if a nested operational state machine is used, poll `FaultController::check_transitions()`, not + the child machine directly + +### 1.6 Runtime Diagnostics API + +The runtime diagnostic façade is: + +- `PANIC(...)` +- `FAULT(...)` +- `WARNING(...)` +- `INFO(...)` + +Their semantics are: + +- `PANIC(...)`: fatal runtime/internal error, enters the global `FAULT` +- `FAULT(...)`: fatal domain/application fault, enters the global `FAULT` +- `WARNING(...)`: non-fatal diagnostic +- `INFO(...)`: informational diagnostic + +`PANIC(...)` and `FAULT(...)` both call the same global fault path underneath. +The difference is semantic classification of the cause and diagnostic category. + +### 1.7 Structured Fault Requests + +When the fatal condition is already available as structured data, use: + +```cpp +FaultController::request_fault(FaultCause::external("origin", "message")); +``` + +This is the primitive used internally by protections and fatal runtime reporters. + +### 1.8 Transmission Semantics + +All external reporting goes through `Diagnostics`. + +There is no separate fault-broadcast subsystem anymore. + +The transmission model is: + +- normal diagnostics are queued with `NORMAL` priority +- faults are published with `URGENT` priority +- `Diagnostics::Hub::flush()` always drains urgent records first + +Default sinks are installed during `Board::init()`: + +- UART sink when UART printing is available +- TCP sink when `STLIB_ETH` is enabled + +If a transport is not compiled in, it is simply not installed. + +## 2. Internal Development + +### 2.1 Architectural Overview + +The design is split into four concerns: + +- **protections**: evaluate domain rules over samples +- **faulting**: control the global `OPERATIONAL/FAULT` runtime +- **diagnostics**: store and dispatch structured records +- **transport**: serialize and emit diagnostics + +```mermaid +flowchart LR + A["ProtectionEngine"] --> B["FaultController::request_fault(...)"] + A --> C["Diagnostics::Hub"] + D["PANIC / FAULT"] --> B + E["WARNING / INFO"] --> C + B --> F["FaultCause"] + F --> G["FaultDiagnosticMapper"] + G --> C + C --> H["DiagnosticSink"] + H --> I["UART / TCP"] +``` + +The key boundaries are: + +- protections do not know transport +- diagnostics do not own or evaluate protections +- `FaultController` does not own sinks +- transport does not change system state + +### 2.2 Protection Domain Model + +Public API: + +- `ProtectionEngine::create_protection(...)` +- `ProtectionHandle::add_rule(...)` +- `Protections::Rules::*` + +Internal model: + +- one flat collection of protections +- no low/high frequency split in the domain model +- rule configuration returned through `std::expected` +- rule evaluation produces `RuleState`, `RuleEdge`, and `RuleSnapshot` + +Supported rule kinds: + +- `BELOW` +- `ABOVE` +- `RANGE` +- `EQUALS` +- `NOT_EQUALS` +- `TIME_ACCUMULATION` + +`ProtectionEngine::evaluate()`: + +- walks every protection +- publishes non-fatal rule edges through `Diagnostics` +- requests the global fault when a rule reaches `FAULT` +- throttles repeated fault notifications with `notify_delay_in_microseconds` + +### 2.3 Global Fault Runtime + +`FaultController` owns the global runtime state machine. + +The runtime machine is: + +- always present +- always two-state: `OPERATIONAL` / `FAULT` +- optionally composed with a nested operational machine through `FaultPolicy` + +Responsibilities of `FaultController`: + +- own and start the global runtime +- latch the first `FaultCause` +- request transition to the global `FAULT` +- execute the user `on_fault_enter` callback through the `FAULT` state enter action +- publish the fault diagnostic with urgent priority + +Important invariant: + +- entering the global `FAULT` never depends on transport delivery succeeding + +### 2.4 Early-Fault Bootstrap Semantics + +The fault path is valid during `Board::init()`. + +That is why `Board::init()` installs: + +- default diagnostic sinks +- the global fault runtime + +before subsystem initialization that may trigger `PANIC(...)`. + +If a fatal request arrives before the global runtime has been started: + +- the cause is latched +- the runtime is rebuilt so that it starts directly in `FAULT` +- the urgent fault diagnostic is still published through `Diagnostics` + +This avoids losing early boot faults. + +### 2.5 FaultCause and Diagnostic Mapping + +`FaultCause` is not a `DiagnosticRecord`. + +`FaultCause` is the control-plane object for fatal conditions. +It stores: + +- fault kind +- stable origin +- runtime fault payload or protection fault payload + +`Diagnostics::DiagnosticRecord` is the reporting-plane object. + +Conversion between both is explicit: + +- `FaultController` latches and operates on `FaultCause` +- `FaultDiagnosticMapper` converts `FaultCause` into a `DiagnosticRecord` +- `Diagnostics::Hub` only stores and delivers `DiagnosticRecord` + +This keeps the global fault runtime independent from the storage and transport shape of diagnostics. + +### 2.6 Diagnostics Model + +`Diagnostics::Hub` is a fixed-capacity internal event bus. + +Main types: + +- `DiagnosticRecord` +- `RuntimeDiagnosticPayload` +- `ProtectionDiagnosticPayload` +- `DiagnosticSink` + +Supporting components: + +- `RecordFactory` +- `DiagnosticFormatter` +- `DiagnosticTimestampProvider` + +Memory policy: + +- fixed sink storage +- fixed history ring +- fixed pending queue +- no heap in `publish()` +- no heap in `flush()` + +Priority policy: + +- `NORMAL` +- `URGENT` + +`flush_urgent()` drains urgent records only. +`flush()` drains urgent first and then normal records. + +### 2.7 Runtime Reporters + +The runtime reporters are intentionally thin façades. + +`PANIC(...)`, `FAULT(...)`, `WARNING(...)`, and `INFO(...)`: + +- capture source metadata with `std::source_location` +- format the runtime message into a fixed stack buffer +- publish a diagnostic or request a fault + +They do not use shared mutable metadata anymore. +That keeps the reporting path reentrant and removes the old `SetMetadata + Trigger` split. + +### 2.8 Timestamp Semantics + +`DiagnosticTimestampProvider` does not start RTC services from the diagnostic hot path. + +If RTC is already running and has valid time, records use RTC timestamp data. +Otherwise, diagnostics fall back to uptime when available. + +This avoids recursive or bootstrap-dependent fatal paths while timestamping diagnostics. + +### 2.9 C++23 Design Choices + +This subsystem uses a narrow set of C++23 features where they provide direct value: + +- `std::expected` + for explicit registration/configuration failure +- `std::variant` and `std::visit` + for static rule composition +- `concepts` + to constrain rule factories and sample sources +- `std::source_location` + for runtime reporter metadata without mutable globals +- `std::to_underlying` + for transport encoding +- `std::byteswap` and `std::endian` + in the TCP diagnostic encoder +- `std::span` + for fixed binary transport encoding + +The intent is not to maximize feature usage. +The intent is to improve: + +- correctness +- API clarity +- determinism +- suitability for embedded firmware + +### 2.10 Firmware Invariants + +The subsystem is expected to preserve these invariants: + +- no heap in protection evaluation +- no heap in diagnostic publish/flush +- no shared mutable metadata in runtime reporters +- no transport logic inside protection rules +- no separate fault-broadcast path outside `Diagnostics` +- fixed-capacity storage for protections, sinks, history, and pending queue +- explicit lifecycle: register, init, evaluate, flush From 5b4278ea45ed748a08b6fd52f01ad2c30493665b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Fri, 17 Apr 2026 19:20:44 +0200 Subject: [PATCH 10/23] Constrain global fault entry API --- .../Protections/FaultController.hpp | 27 ++++++++++--------- .../Protections/FaultController.cpp | 17 ------------ Tests/TestAccess.hpp | 4 +++ Tests/diagnostics_test.cpp | 17 +++++++----- docs/protections-and-diagnostics.md | 16 ++++++----- 5 files changed, 39 insertions(+), 42 deletions(-) diff --git a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp index 4dc2e5e3b..767caeaa6 100644 --- a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp +++ b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp @@ -4,6 +4,14 @@ #include "ST-LIB_HIGH/Protections/ProtectionTypes.hpp" #include "StateMachine/StateMachine.hpp" +namespace ST_LIB::TestAccess { +struct FaultController; +} + +class PanicReporter; +class FaultReporter; +class ProtectionEngine; + namespace FaultConfig { inline constexpr size_t origin_capacity = Protections::Config::max_name_length; inline constexpr size_t runtime_message_capacity = 160; @@ -11,7 +19,7 @@ inline constexpr size_t function_capacity = 64; inline constexpr size_t file_capacity = 96; } // namespace FaultConfig -enum class FaultCauseKind : uint8_t { PANIC = 0, RUNTIME_FAULT, PROTECTION, EXTERNAL }; +enum class FaultCauseKind : uint8_t { PANIC = 0, RUNTIME_FAULT, PROTECTION }; struct FaultRuntimePayload { uint32_t line{0}; @@ -46,21 +54,11 @@ struct FaultCause { const char* func, const char* file ); - static inline FaultCause runtime_error( - const char* message, - bool truncated, - int line, - const char* func, - const char* file - ) { - return panic(message, truncated, line, func, file); - } static FaultCause protection( const char* protection_name, Protections::RuleEdge edge, const Protections::RuleSnapshot& snapshot ); - static FaultCause external(const char* origin, const char* message); }; class FaultController { @@ -87,11 +85,15 @@ class FaultController { static void start(); static void check_transitions(); - static void request_fault(const FaultCause& cause); static bool is_faulted(); static const FaultCause* latched_fault_cause(); private: + friend class PanicReporter; + friend class FaultReporter; + friend class ProtectionEngine; + friend struct ST_LIB::TestAccess::FaultController; + enum class RuntimeState : uint8_t { OPERATIONAL = 0, FAULT = 1 }; struct RuntimeStorage { @@ -169,6 +171,7 @@ class FaultController { static void reset_runtime_storage(); static void publish_fault_diagnostic(const FaultCause& cause); + static void request_fault(const FaultCause& cause); static void on_fault_state_enter(); static RuntimeStorage runtime_storage; diff --git a/Src/ST-LIB_HIGH/Protections/FaultController.cpp b/Src/ST-LIB_HIGH/Protections/FaultController.cpp index 87be2998f..ca8b1689a 100644 --- a/Src/ST-LIB_HIGH/Protections/FaultController.cpp +++ b/Src/ST-LIB_HIGH/Protections/FaultController.cpp @@ -59,7 +59,6 @@ Diagnostics::DiagnosticRecord to_record(const FaultCause& cause) { Diagnostics::DiagnosticPriority::URGENT ); case FaultCauseKind::RUNTIME_FAULT: - case FaultCauseKind::EXTERNAL: return Diagnostics::RecordFactory::runtime_fault( cause.runtime.message, cause.runtime.truncated, @@ -144,22 +143,6 @@ FaultCause FaultCause::protection( return cause; } -FaultCause FaultCause::external(const char* origin, const char* message) { - FaultCause cause{}; - cause.kind = FaultCauseKind::EXTERNAL; - copy_c_string(cause.origin, origin); - cause.runtime.truncated = false; - cause.runtime.line = 0; - copy_c_string( - cause.runtime.message, - message == nullptr ? "external fault" : message, - &cause.runtime.truncated - ); - copy_c_string(cause.runtime.function_name, "FaultCause::external"); - copy_c_string(cause.runtime.file_name, __FILE__); - return cause; -} - void FaultController::reset_runtime_storage() { if (runtime_storage.machine != nullptr && runtime_storage.destroy != nullptr) { runtime_storage.destroy(runtime_storage.machine); diff --git a/Tests/TestAccess.hpp b/Tests/TestAccess.hpp index 550408f23..de2f05011 100644 --- a/Tests/TestAccess.hpp +++ b/Tests/TestAccess.hpp @@ -40,4 +40,8 @@ struct ProtectionEngine { } }; +struct FaultController { + static void request_fault(const ::FaultCause& cause) { ::FaultController::request_fault(cause); } +}; + } // namespace ST_LIB::TestAccess diff --git a/Tests/diagnostics_test.cpp b/Tests/diagnostics_test.cpp index a0d10ed0c..67963ad8f 100644 --- a/Tests/diagnostics_test.cpp +++ b/Tests/diagnostics_test.cpp @@ -74,6 +74,10 @@ void reset_operational_machine() { void on_fault_enter() { fault_enter_calls++; } +FaultCause make_test_runtime_fault(const char* message) { + return FaultCause::runtime_fault(message, false, 0, "diagnostics_test", "diagnostics_test.cpp"); +} + uint32_t emit_warning_and_return_line() { constexpr uint32_t expected_line = __LINE__ + 1; WARNING("source location warning"); @@ -167,7 +171,7 @@ TEST_F(DiagnosticsHubTest, PendingQueueIsBoundedWhenASinkNeverDelivers) { } TEST_F(DiagnosticsHubTest, ReinstallingRuntimeClearsLatchedFaultState) { - FaultController::request_fault(FaultCause::external("test", "first fault")); + TestAccess::FaultController::request_fault(make_test_runtime_fault("first fault")); ASSERT_TRUE(FaultController::is_faulted()); ASSERT_NE(FaultController::latched_fault_cause(), nullptr); @@ -180,13 +184,14 @@ TEST_F(DiagnosticsHubTest, ReinstallingRuntimeClearsLatchedFaultState) { } TEST_F(DiagnosticsHubTest, FaultControllerTransitionsOnlyOnce) { - FaultController::request_fault(FaultCause::external("test", "fault once")); - FaultController::request_fault(FaultCause::external("test", "fault twice")); + TestAccess::FaultController::request_fault(make_test_runtime_fault("fault once")); + TestAccess::FaultController::request_fault(make_test_runtime_fault("fault twice")); ASSERT_TRUE(FaultController::is_faulted()); ASSERT_NE(FaultController::latched_fault_cause(), nullptr); EXPECT_EQ(fault_enter_calls, 1u); - EXPECT_STREQ(FaultController::latched_fault_cause()->origin, "test"); + EXPECT_EQ(FaultController::latched_fault_cause()->kind, FaultCauseKind::RUNTIME_FAULT); + EXPECT_STREQ(FaultController::latched_fault_cause()->runtime.message, "fault once"); } TEST_F(DiagnosticsHubTest, FaultControllerDelegatesToOperationalMachineWhileOperational) { @@ -205,7 +210,7 @@ TEST_F(DiagnosticsHubTest, FaultBeforeStartStartsRuntimeDirectlyInFault) { FaultController::install_runtime(); reset_operational_machine(); - FaultController::request_fault(FaultCause::external("test", "fault before start")); + TestAccess::FaultController::request_fault(make_test_runtime_fault("fault before start")); EXPECT_TRUE(FaultController::is_faulted()); EXPECT_EQ(fault_enter_calls, 0u); @@ -224,7 +229,7 @@ TEST_F(DiagnosticsHubTest, FaultControllerStopsDelegatingAfterFault) { reset_operational_machine(); FaultController::start(); - FaultController::request_fault(FaultCause::external("test", "stop delegating")); + TestAccess::FaultController::request_fault(make_test_runtime_fault("stop delegating")); transition_to_hold = true; FaultController::check_transitions(); diff --git a/docs/protections-and-diagnostics.md b/docs/protections-and-diagnostics.md index 85ddf6a07..b31aa0f83 100644 --- a/docs/protections-and-diagnostics.md +++ b/docs/protections-and-diagnostics.md @@ -28,7 +28,7 @@ polls: The global fault model is always the same: - the framework owns a global runtime with two states: `OPERATIONAL` and `FAULT` -- the only valid way to enter the global `FAULT` state is `FaultController::request_fault(...)` +- internally, the only way to enter the global `FAULT` state is `FaultController::request_fault(...)` - protection faults, `PANIC(...)`, and `FAULT(...)` all end up there - fault diagnostics are transmitted with urgent priority through `Diagnostics` @@ -166,8 +166,8 @@ Important rules: - the user state machine models operational behavior only - the user does not program transitions to the global `FAULT` -- if a fatal condition must force the system into `FAULT`, use `PANIC(...)`, `FAULT(...)`, or - `FaultController::request_fault(...)` +- if a fatal condition must force the system into `FAULT`, user code should use `PANIC(...)` or + `FAULT(...)` - if a nested operational state machine is used, poll `FaultController::check_transitions()`, not the child machine directly @@ -190,15 +190,17 @@ Their semantics are: `PANIC(...)` and `FAULT(...)` both call the same global fault path underneath. The difference is semantic classification of the cause and diagnostic category. -### 1.7 Structured Fault Requests +### 1.7 Internal Fault Primitive -When the fatal condition is already available as structured data, use: +Internally, protections and fatal runtime reporters converge on: ```cpp -FaultController::request_fault(FaultCause::external("origin", "message")); +FaultController::request_fault(cause); ``` -This is the primitive used internally by protections and fatal runtime reporters. +This primitive is not intended to be the normal user-facing API. +User code should prefer `FAULT(...)` or `PANIC(...)` so the library captures consistent source +metadata and preserves the public runtime contract. ### 1.8 Transmission Semantics From 6979532a5a322af4675d980a2370f0f9441bcd32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Fri, 17 Apr 2026 19:55:56 +0200 Subject: [PATCH 11/23] Make Board take an explicit fault policy type --- Inc/ST-LIB.hpp | 46 ++++++----------------------------- docs/st-lib-board-contract.md | 22 ++++++++++++++--- 2 files changed, 26 insertions(+), 42 deletions(-) diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 8c5067fe8..fad4a1e33 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -36,47 +36,23 @@ template struct Faul "FaultPolicy operational machine must be a StateMachine" ); - using fault_policy_tag = void; static constexpr bool has_operational_machine = true; static constexpr auto& operational_machine = OperationalMachine; static constexpr Callback on_fault_enter = OnFaultEnter; - - template consteval std::size_t inscribe(Ctx&) const { return 0; } }; template struct FaultPolicyNoMachine { - using fault_policy_tag = void; static constexpr bool has_operational_machine = false; static constexpr Callback on_fault_enter = OnFaultEnter; - - template consteval std::size_t inscribe(Ctx&) const { return 0; } }; -template struct is_fault_policy_type : std::false_type {}; - -template -struct is_fault_policy_type> : std::true_type {}; - -template -struct is_fault_policy_type> : std::true_type {}; - -template -inline constexpr bool is_fault_policy_type_v = is_fault_policy_type>::value; - using DefaultFaultPolicy = FaultPolicyNoMachine<>; -template struct select_fault_policy_type { - using type = DefaultPolicy; -}; - -template -struct select_fault_policy_type { - using CleanT = std::remove_cvref_t; - using type = std::conditional_t< - is_fault_policy_type_v, - CleanT, - typename select_fault_policy_type::type>; -}; +template +concept BoardFaultPolicy = requires { + { Policy::has_operational_machine } -> std::convertible_to; + { Policy::on_fault_enter } -> std::convertible_to; +} && (!Policy::has_operational_machine || requires { Policy::operational_machine; }); // The contract of BuildCtx/Board is documented in docs/st-lib-board-contract.md. template struct BuildCtx { @@ -189,15 +165,7 @@ consteval std::array build_dma_configs( } // namespace BuildUtils -template struct Board { -private: - static constexpr std::size_t fault_policy_count = - ((is_fault_policy_type_v> ? 1u : 0u) + ... + 0u); - static_assert(fault_policy_count <= 1, "Board supports at most one FaultPolicy"); - - using SelectedFaultPolicy = - typename select_fault_policy_type::type; - +template struct Board { public: static consteval auto build_ctx() { DomainsCtx ctx{}; @@ -325,7 +293,7 @@ template struct Board { HALconfig::peripheral_clock(); Diagnostics::Runtime::install_default_sinks(); - FaultController::template install_runtime(); + FaultController::template install_runtime(); #ifdef HAL_RTC_MODULE_ENABLED (void)Global_RTC::ensure_started(); diff --git a/docs/st-lib-board-contract.md b/docs/st-lib-board-contract.md index 404d99871..68d1dc728 100644 --- a/docs/st-lib-board-contract.md +++ b/docs/st-lib-board-contract.md @@ -7,13 +7,27 @@ If you change a domain, add a new domain, or add a cross-domain composition rule ## 1. Mental Model -`Board<...>` is a compile-time build pipeline plus a runtime init pipeline. +`Board` is a compile-time build pipeline plus a runtime init pipeline. - Compile time decides what exists and how it must be configured. - Runtime only materializes already-built configurations and links HAL handles. `Board` is intentionally declarative. Request objects describe intent; domains convert that intent -into concrete configs. +into concrete configs. The first template argument is not a request object: it is the global fault +runtime policy type used by `FaultController`. + +## 1.1 Board Policy Contract + +The first template argument of `Board` must be a fault policy type. + +That type must expose: + +- `static constexpr bool has_operational_machine` +- `static constexpr Callback on_fault_enter` +- `static constexpr auto& operational_machine` when `has_operational_machine == true` + +`FaultPolicy<...>`, `FaultPolicyNoMachine<...>`, and `DefaultFaultPolicy` are the intended public +helpers for this contract. ## 2. Domain Contract @@ -35,7 +49,8 @@ signature. ## 3. Request Object Contract -A request object that can be used inside `Board<...>` must provide: +A request object that can be used after the first `Policy` argument inside `Board` must +provide: - `using domain = ;` - `template consteval std::size_t inscribe(Ctx&) const` @@ -68,6 +83,7 @@ This is a deliberate design choice. `BuildCtx` is a storage and ownership map, n - creates a `DomainsCtx` - evaluates every request object's `inscribe(ctx)` in declaration order +- does not inspect or route the `Policy` through `BuildCtx` `Board::build()`: From 7a29c2cbfc3a21459b29f76a0ee9f2c9d8e94f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Fri, 17 Apr 2026 19:56:11 +0200 Subject: [PATCH 12/23] Make time_accumulation use scheduler time --- .../Services/Diagnostics/Diagnostics.hpp | 2 +- Inc/ST-LIB_HIGH/Protections/Protection.hpp | 101 ++++++++++-------- .../Protections/ProtectionTypes.hpp | 3 +- Inc/ST-LIB_HIGH/Protections/Rules.hpp | 45 ++------ .../Diagnostics/DiagnosticFormatter.cpp | 4 +- .../Services/Diagnostics/DiagnosticsHub.cpp | 2 +- Tests/diagnostics_test.cpp | 78 ++++++++++++++ docs/protections-and-diagnostics.md | 18 +++- 8 files changed, 162 insertions(+), 91 deletions(-) diff --git a/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp index 301c12576..cf1509986 100644 --- a/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp +++ b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp @@ -63,7 +63,7 @@ struct ProtectionDiagnosticPayload { bool has_threshold_b{false}; bool uses_warning_threshold{false}; float time_window_s{0.0f}; - float sample_rate_hz{0.0f}; + float active_time_s{0.0f}; }; union DiagnosticPayload { diff --git a/Inc/ST-LIB_HIGH/Protections/Protection.hpp b/Inc/ST-LIB_HIGH/Protections/Protection.hpp index f5d0b3620..53cdc394e 100644 --- a/Inc/ST-LIB_HIGH/Protections/Protection.hpp +++ b/Inc/ST-LIB_HIGH/Protections/Protection.hpp @@ -3,6 +3,7 @@ #include #include "C++Utilities/CppUtils.hpp" +#include "HALAL/Services/Time/Scheduler.hpp" #include "ST-LIB_HIGH/Protections/ProtectionErrors.hpp" #include "ST-LIB_HIGH/Protections/ProtectionTypes.hpp" #include "ST-LIB_HIGH/Protections/Rules.hpp" @@ -68,14 +69,14 @@ class RuleSnapshotBuilder { T fault_threshold, optional warning_threshold, float time_window_s = 0.0f, - float sample_rate_hz = 0.0f + float active_time_s = 0.0f ) { RuleSnapshot snapshot{}; snapshot.kind = kind; snapshot.sample_encoding = sample_encoding_for(); snapshot.observed_value = to_numeric_value(observed); snapshot.time_window_s = time_window_s; - snapshot.sample_rate_hz = sample_rate_hz; + snapshot.active_time_s = active_time_s; snapshot.uses_warning_threshold = should_use_warning_threshold( current_state, edge, @@ -192,37 +193,48 @@ template struct TimeAccumulationEvaluator { static RuleState compute( const T& sample, const TimeAccumulationRuleConfig& config, - array& accumulation_window, - size_t configured_window_samples, - size_t& window_fill_count, - size_t& window_index, - T& rolling_sum, - T& average_value + uint64_t configured_window_us, + bool& has_last_tick, + uint64_t& last_tick_us, + uint64_t& warning_active_time_us, + uint64_t& fault_active_time_us, + T& active_magnitude, + float& active_time_s ) { - const T magnitude = detail::absolute_value(sample); - const size_t window_samples = configured_window_samples == 0 ? 1 : configured_window_samples; - - if (window_fill_count < window_samples) { - rolling_sum += magnitude; - accumulation_window[window_fill_count] = magnitude; - window_fill_count++; - average_value = static_cast(rolling_sum / static_cast(window_fill_count)); - return RuleState::NORMAL; + const uint64_t now_us = Scheduler::get_global_tick(); + const uint64_t elapsed_us = has_last_tick ? (now_us - last_tick_us) : 0ULL; + has_last_tick = true; + last_tick_us = now_us; + + active_magnitude = detail::absolute_value(sample); + + if (detail::is_above(active_magnitude, config.fault_threshold)) { + fault_active_time_us += elapsed_us; + } else { + fault_active_time_us = 0; } - rolling_sum -= accumulation_window[window_index]; - accumulation_window[window_index] = magnitude; - rolling_sum += magnitude; - window_index = (window_index + 1) % window_samples; - average_value = static_cast(rolling_sum / static_cast(window_samples)); + if (config.warning_threshold.has_value() && + detail::is_above(active_magnitude, config.warning_threshold.value())) { + warning_active_time_us += elapsed_us; + } else { + warning_active_time_us = 0; + } - if (detail::is_above(average_value, config.fault_threshold)) { + if (fault_active_time_us >= configured_window_us) { + active_time_s = static_cast(fault_active_time_us) / 1'000'000.0f; return RuleState::FAULT; } - if (config.warning_threshold.has_value() && - detail::is_above(average_value, config.warning_threshold.value())) { + if (config.warning_threshold.has_value() && warning_active_time_us >= configured_window_us) { + active_time_s = static_cast(warning_active_time_us) / 1'000'000.0f; return RuleState::WARNING; } + + active_time_s = static_cast( + config.warning_threshold.has_value() ? warning_active_time_us + : fault_active_time_us + ) / + 1'000'000.0f; return RuleState::NORMAL; } }; @@ -342,10 +354,11 @@ template struct NotEqualsRule { template struct TimeAccumulationRule { explicit TimeAccumulationRule(TimeAccumulationRuleConfig config) : config(config) { - configured_window_samples = - static_cast(std::lround(config.time_window_s * config.sample_rate_hz)); - if (configured_window_samples == 0) { - configured_window_samples = 1; + configured_window_us = static_cast(std::llround( + static_cast(config.time_window_s) * 1'000'000.0 + )); + if (configured_window_us == 0) { + configured_window_us = 1; } } @@ -354,12 +367,13 @@ template struct TimeAccumulationRule { const RuleState state = TimeAccumulationEvaluator::compute( sample, config, - accumulation_window, - configured_window_samples, - window_fill_count, - window_index, - rolling_sum, - average_value + configured_window_us, + has_last_tick, + last_tick_us, + warning_active_time_us, + fault_active_time_us, + active_magnitude, + active_time_s ); const RuleEdge edge = tracker.advance(state); return { @@ -367,26 +381,27 @@ template struct TimeAccumulationRule { .edge = edge, .snapshot = RuleSnapshotBuilder::single_threshold( RuleKind::TIME_ACCUMULATION, - average_value, + active_magnitude, state, edge, previous_state, config.fault_threshold, config.warning_threshold, config.time_window_s, - config.sample_rate_hz + active_time_s ), }; } TimeAccumulationRuleConfig config{}; RuleStateTracker tracker{}; - size_t configured_window_samples{1}; - array accumulation_window{}; - size_t window_fill_count{0}; - size_t window_index{0}; - T rolling_sum{detail::zero_value()}; - T average_value{detail::zero_value()}; + uint64_t configured_window_us{1}; + bool has_last_tick{false}; + uint64_t last_tick_us{0}; + uint64_t warning_active_time_us{0}; + uint64_t fault_active_time_us{0}; + T active_magnitude{detail::zero_value()}; + float active_time_s{0.0f}; }; template > diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionTypes.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionTypes.hpp index 490c466b4..35bf4a773 100644 --- a/Inc/ST-LIB_HIGH/Protections/ProtectionTypes.hpp +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionTypes.hpp @@ -8,7 +8,6 @@ namespace Protections { namespace Config { inline constexpr size_t max_protections = 32; inline constexpr size_t max_rules_per_protection = 16; -inline constexpr size_t max_time_accumulation_samples = 128; inline constexpr uint64_t notify_delay_in_microseconds = 2'000'000ULL; inline constexpr size_t max_name_length = 48; } // namespace Config @@ -49,7 +48,7 @@ struct RuleSnapshot { bool has_threshold_b{false}; bool uses_warning_threshold{false}; float time_window_s{0.0f}; - float sample_rate_hz{0.0f}; + float active_time_s{0.0f}; }; struct RuleEvaluation { diff --git a/Inc/ST-LIB_HIGH/Protections/Rules.hpp b/Inc/ST-LIB_HIGH/Protections/Rules.hpp index c7b1e593f..057e0b4a2 100644 --- a/Inc/ST-LIB_HIGH/Protections/Rules.hpp +++ b/Inc/ST-LIB_HIGH/Protections/Rules.hpp @@ -35,7 +35,6 @@ template struct TimeAccumulationRuleConfig { T fault_threshold{}; optional warning_threshold{}; float time_window_s{0.0f}; - float sample_rate_hz{0.0f}; }; template @@ -159,8 +158,7 @@ template constexpr expected, RuleConfigError> validate_time_accumulation( T fault_threshold, optional warning_threshold, - float window_seconds, - float sample_rate_hz + float window_seconds ) { const auto window_validation = validate_with_consteval( [&] { return window_seconds > 0.0f; }, @@ -171,23 +169,6 @@ constexpr expected, RuleConfigError> validate_time_accumulatio return unexpected(window_validation.error()); } - const auto rate_validation = validate_with_consteval( - [&] { return sample_rate_hz > 0.0f; }, - RuleConfigError::INVALID_SAMPLE_RATE, - "time_accumulation requires a positive sample rate" - ); - if (!rate_validation.has_value()) { - return unexpected(rate_validation.error()); - } - - const auto window_samples = static_cast(std::lround(window_seconds * sample_rate_hz)); - if (window_samples == 0) { - return unexpected(RuleConfigError::INVALID_WINDOW); - } - if (window_samples > Config::max_time_accumulation_samples) { - return unexpected(RuleConfigError::WINDOW_CAPACITY_EXCEEDED); - } - if (warning_threshold.has_value()) { const auto warning_validation = validate_with_consteval( [&] { return warning_threshold.value() <= fault_threshold; }, @@ -203,7 +184,6 @@ constexpr expected, RuleConfigError> validate_time_accumulatio .fault_threshold = fault_threshold, .warning_threshold = warning_threshold, .time_window_s = window_seconds, - .sample_rate_hz = sample_rate_hz, }}; } @@ -255,29 +235,20 @@ constexpr expected, RuleConfigError> not_equals(T value) { } template -constexpr expected, RuleConfigError> -time_accumulation(T fault_threshold, float window_seconds, float sample_rate_hz) { - return detail::validate_time_accumulation( - fault_threshold, - nullopt, - window_seconds, - sample_rate_hz - ); +constexpr expected, RuleConfigError> time_accumulation( + T fault_threshold, + float window_seconds +) { + return detail::validate_time_accumulation(fault_threshold, nullopt, window_seconds); } template constexpr expected, RuleConfigError> time_accumulation( T fault_threshold, T warning_threshold, - float window_seconds, - float sample_rate_hz + float window_seconds ) { - return detail::validate_time_accumulation( - fault_threshold, - warning_threshold, - window_seconds, - sample_rate_hz - ); + return detail::validate_time_accumulation(fault_threshold, warning_threshold, window_seconds); } } // namespace Rules diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp index 410ed67ad..bc23e42f7 100644 --- a/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp +++ b/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp @@ -237,9 +237,9 @@ void format_protection_record( append_formatted( buffer, buffer_size, - " | Window: %.3fs | Rate: %.3fHz", + " | Window: %.3fs | Active: %.3fs", static_cast(protection.time_window_s), - static_cast(protection.sample_rate_hz) + static_cast(protection.active_time_s) ); } append_timestamp_suffix(record.timestamp, buffer, buffer_size); diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp index 6811b1d7a..549a606d2 100644 --- a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp +++ b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp @@ -222,7 +222,7 @@ DiagnosticRecord RecordFactory::protection_event( record.payload.protection.has_threshold_b = snapshot.has_threshold_b; record.payload.protection.uses_warning_threshold = snapshot.uses_warning_threshold; record.payload.protection.time_window_s = snapshot.time_window_s; - record.payload.protection.sample_rate_hz = snapshot.sample_rate_hz; + record.payload.protection.active_time_s = snapshot.active_time_s; return record; } diff --git a/Tests/diagnostics_test.cpp b/Tests/diagnostics_test.cpp index 67963ad8f..7c814d578 100644 --- a/Tests/diagnostics_test.cpp +++ b/Tests/diagnostics_test.cpp @@ -3,6 +3,7 @@ #include "ErrorHandler/ErrorHandler.hpp" #include "HALAL/Services/Diagnostics/Diagnostics.hpp" #include "HALAL/Services/InfoWarning/InfoWarning.hpp" +#include "HALAL/Services/Time/Scheduler.hpp" #include "ST-LIB_HIGH/Protections/FaultController.hpp" #include "ST-LIB_HIGH/Protections/ProtectionEngine.hpp" #include "ST-LIB_HIGH/Protections/Rules.hpp" @@ -103,6 +104,8 @@ class DiagnosticsHubTest : public ::testing::Test { reset_operational_machine(); TestPanicReporter::reset(); fault_enter_calls = 0; + Scheduler::global_tick_us_ = 0; + Scheduler_global_timer = nullptr; FaultController::install_runtime(); FaultController::start(); @@ -269,6 +272,77 @@ TEST_F(DiagnosticsHubTest, ProtectionEngineEvaluatesRulesAndPublishesSnapshots) ); } +TEST_F(DiagnosticsHubTest, TimeAccumulationUsesSchedulerTickForContinuousDuration) { + float monitored_value = 0.0f; + SampleSource source(monitored_value); + + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + + auto protection = ProtectionEngine::create_protection("time_value", source); + ASSERT_TRUE(protection.has_value()); + ASSERT_TRUE(protection->add_rule(Protections::Rules::time_accumulation(10.0f, 0.001f)).has_value()); + + ProtectionEngine::initialize(); + + monitored_value = 12.0f; + Scheduler::global_tick_us_ = 0; + ProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); + + Scheduler::global_tick_us_ = 500; + ProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); + + Scheduler::global_tick_us_ = 1'000; + ProtectionEngine::evaluate(); + Diagnostics::Hub::flush(); + + ASSERT_FALSE(sink->records.empty()); + EXPECT_TRUE(FaultController::is_faulted()); + EXPECT_EQ(sink->records.back().category, Diagnostics::Category::PROTECTION_EVENT); + EXPECT_EQ(sink->records.back().payload.protection.rule_kind, Protections::RuleKind::TIME_ACCUMULATION); + EXPECT_FLOAT_EQ(sink->records.back().payload.protection.time_window_s, 0.001f); + EXPECT_FLOAT_EQ(sink->records.back().payload.protection.active_time_s, 0.001f); +} + +TEST_F(DiagnosticsHubTest, TimeAccumulationResetsWhenConditionClears) { + float monitored_value = 0.0f; + SampleSource source(monitored_value); + + auto protection = ProtectionEngine::create_protection("time_reset_value", source); + ASSERT_TRUE(protection.has_value()); + ASSERT_TRUE(protection->add_rule(Protections::Rules::time_accumulation(10.0f, 0.001f)).has_value()); + + ProtectionEngine::initialize(); + + monitored_value = 12.0f; + Scheduler::global_tick_us_ = 0; + ProtectionEngine::evaluate(); + + Scheduler::global_tick_us_ = 700; + ProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); + + monitored_value = 0.0f; + Scheduler::global_tick_us_ = 800; + ProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); + + Scheduler::global_tick_us_ = 1'600; + ProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); + + monitored_value = 12.0f; + Scheduler::global_tick_us_ = 1'600; + ProtectionEngine::evaluate(); + + Scheduler::global_tick_us_ = 2'300; + ProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); +} + TEST_F(DiagnosticsHubTest, PanicPublishesAndEntersFault) { auto sink_result = Diagnostics::Hub::emplace_sink(); ASSERT_TRUE(sink_result.has_value()); @@ -355,6 +429,10 @@ TEST_F(DiagnosticsHubTest, RejectsInvalidRuleConfigurationsWithoutGlobalSideEffe auto invalid_rule = Protections::Rules::below(1.0f, 0.5f); EXPECT_FALSE(invalid_rule.has_value()); EXPECT_EQ(invalid_rule.error(), Protections::RuleConfigError::INVALID_WARNING_THRESHOLD); + + auto invalid_time_rule = Protections::Rules::time_accumulation(10.0f, 0.0f); + EXPECT_FALSE(invalid_time_rule.has_value()); + EXPECT_EQ(invalid_time_rule.error(), Protections::RuleConfigError::INVALID_WINDOW); } } // namespace diff --git a/docs/protections-and-diagnostics.md b/docs/protections-and-diagnostics.md index b31aa0f83..bce5b8976 100644 --- a/docs/protections-and-diagnostics.md +++ b/docs/protections-and-diagnostics.md @@ -34,7 +34,7 @@ The global fault model is always the same: ```mermaid flowchart TD - A["Register protections"] --> B["Declare Board and optional FaultPolicy"] + A["Register protections"] --> B["Declare Board policy and request objects"] B --> C["Board::init()"] C --> D["while (1)"] D --> E["FaultController::check_transitions()"] @@ -86,7 +86,7 @@ After `Board::init()`, the protection registry is locked. using namespace ST_LIB; constexpr auto led = DigitalOutputDomain::DigitalOutput(PF13); -using MainBoard = Board; +using MainBoard = Board; float bus_voltage = 0.0f; @@ -105,7 +105,7 @@ int main() { } if (!protection->add_rule( - Protections::Rules::time_accumulation(20.0f, 15.0f, 0.5f, 10000.0f) + Protections::Rules::time_accumulation(20.0f, 15.0f, 0.5f) ) .has_value()) { PANIC("failed to add time_accumulation rule"); @@ -131,6 +131,11 @@ That runtime has two states: If the application does not use a functional state machine, nothing else is required. +Typical choices are: + +- `Board` when no extra fault callback is needed +- `Board, ...>` when only `FAULT` entry actions are needed + If the application does use a functional state machine, it can be nested inside `OPERATIONAL` through a `FaultPolicy`. @@ -148,8 +153,7 @@ static void on_fault_enter() { // disable power stage, set LEDs, open contactors, etc. } -static inline constexpr FaultPolicy fault_policy{}; -using MainBoard = Board; +using MainBoard = Board, led>; int main() { MainBoard::init(); @@ -170,6 +174,7 @@ Important rules: `FAULT(...)` - if a nested operational state machine is used, poll `FaultController::check_transitions()`, not the child machine directly +- `Board` takes the fault policy type as its first template argument ### 1.6 Runtime Diagnostics API @@ -276,6 +281,9 @@ Supported rule kinds: - `NOT_EQUALS` - `TIME_ACCUMULATION` +`TIME_ACCUMULATION` uses `Scheduler::get_global_tick()` to measure real elapsed time. +It no longer assumes a fixed evaluation rate. + `ProtectionEngine::evaluate()`: - walks every protection From 0db347c09d706c9e0374e46d789dd05d867dec6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 22 Apr 2026 20:59:24 +0200 Subject: [PATCH 13/23] applied formatter --- Inc/HALAL/Models/Packets/SPIOrder.hpp | 9 +- Inc/HALAL/Models/SPI/SPI2.hpp | 96 ++++--------------- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 10 +- .../Services/Communication/FDCAN/FDCAN.hpp | 5 +- .../Services/Diagnostics/Diagnostics.hpp | 4 +- .../Services/InfoWarning/InfoWarning.hpp | 24 ++--- Inc/HALAL/Services/PWM/DualPWM.hpp | 12 +-- Inc/HALAL/Services/PWM/PWM.hpp | 6 +- Inc/HALAL/Services/Time/Scheduler.hpp | 20 ++-- .../Protections/FaultController.hpp | 15 +-- Inc/ST-LIB_HIGH/Protections/Protection.hpp | 39 ++++---- .../Protections/ProtectionConcepts.hpp | 20 ++-- .../Protections/ProtectionEngine.hpp | 4 +- Inc/ST-LIB_HIGH/Protections/Rules.hpp | 37 +++---- Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp | 16 +--- Inc/ST-LIB_LOW/Sd/Sd.hpp | 4 +- Src/HALAL/Models/PinModel/Pin.cpp | 5 +- Src/HALAL/Models/TimerDomain/TimerDomain.cpp | 2 +- .../Diagnostics/DiagnosticFormatter.cpp | 12 +-- .../Services/Diagnostics/DiagnosticSinks.cpp | 3 +- .../Services/Diagnostics/DiagnosticsHub.cpp | 34 +++---- Src/HALAL/Services/FMAC/FMAC.cpp | 4 +- Src/HALAL/Services/Time/Scheduler.cpp | 16 ++-- Src/ST-LIB.cpp | 4 +- .../Protections/FaultController.cpp | 4 +- .../Protections/ProtectionEngine.cpp | 5 +- Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp | 4 +- Tests/TestAccess.hpp | 4 +- Tests/diagnostics_test.cpp | 37 ++++--- 29 files changed, 166 insertions(+), 289 deletions(-) diff --git a/Inc/HALAL/Models/Packets/SPIOrder.hpp b/Inc/HALAL/Models/Packets/SPIOrder.hpp index c667fb164..767e3698e 100644 --- a/Inc/HALAL/Models/Packets/SPIOrder.hpp +++ b/Inc/HALAL/Models/Packets/SPIOrder.hpp @@ -78,9 +78,7 @@ class SPIBaseOrder { SPIBaseOrder(uint16_t id, uint16_t master_data_size, uint16_t slave_data_size) : id(id), master_data_size(master_data_size), slave_data_size(slave_data_size) { if (id == 0) { - PANIC( - "Cannot use 0 as the SPIOrderID, as it is reserved to the no Order ready signal" - ); + PANIC("Cannot use 0 as the SPIOrderID, as it is reserved to the no Order ready signal"); } if (master_data_size > slave_data_size) { payload_size = master_data_size + PAYLOAD_OVERHEAD + PAYLOAD_TAIL; @@ -88,10 +86,7 @@ class SPIBaseOrder { payload_size = slave_data_size + PAYLOAD_OVERHEAD + PAYLOAD_TAIL; } if (payload_size > SPI_MAXIMUM_PAYLOAD_SIZE_BYTES) { - PANIC( - "Cannot declare SPIOrder %d as its size surpasses the maximum data size", - id - ); + PANIC("Cannot declare SPIOrder %d as its size surpasses the maximum data size", id); } MISO_payload = new uint8_t[payload_size]{0}; MOSI_payload = new uint8_t[payload_size]{0}; diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index a170ffa74..c3f166f17 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -681,11 +681,7 @@ struct SPIDomain { bool transceive(span tx_data, span rx_data) { size_t size = std::min(tx_data.size_bytes(), rx_data.size_bytes()); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive( @@ -707,11 +703,7 @@ struct SPIDomain { { size_t size = std::min(tx_data.size_bytes(), sizeof(T)); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive( @@ -733,11 +725,7 @@ struct SPIDomain { { size_t size = std::min(sizeof(T), rx_data.size_bytes()); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive( @@ -759,11 +747,7 @@ struct SPIDomain { { size_t size = std::min(sizeof(T1), sizeof(T2)); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive( @@ -809,11 +793,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - PANIC( - "SPI data size (%d) not aligned to frame size (%d)", - sizeof(T), - frame_size - ); + PANIC("SPI data size (%d) not aligned to frame size (%d)", sizeof(T), frame_size); return false; } auto error_code = HAL_SPI_Transmit_DMA( @@ -857,11 +837,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - PANIC( - "SPI data size (%d) not aligned to frame size (%d)", - sizeof(T), - frame_size - ); + PANIC("SPI data size (%d) not aligned to frame size (%d)", sizeof(T), frame_size); return false; } auto error_code = HAL_SPI_Receive_DMA( @@ -885,11 +861,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(tx_data.size_bytes(), rx_data.size_bytes()); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive_DMA( @@ -912,11 +884,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(tx_data.size_bytes(), sizeof(T)); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive_DMA( @@ -943,11 +911,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(sizeof(T), rx_data.size_bytes()); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive_DMA( @@ -970,11 +934,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(sizeof(T1), sizeof(T2)); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive_DMA( @@ -1073,11 +1033,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - PANIC( - "SPI data size (%d) not aligned to frame size (%d)", - sizeof(T), - frame_size - ); + PANIC("SPI data size (%d) not aligned to frame size (%d)", sizeof(T), frame_size); return false; } auto error_code = HAL_SPI_Receive_DMA( @@ -1121,11 +1077,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - PANIC( - "SPI data size (%d) not aligned to frame size (%d)", - sizeof(T), - frame_size - ); + PANIC("SPI data size (%d) not aligned to frame size (%d)", sizeof(T), frame_size); return false; } auto error_code = HAL_SPI_Transmit_DMA( @@ -1149,11 +1101,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(tx_data.size_bytes(), rx_data.size_bytes()); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive_DMA( @@ -1176,11 +1124,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(tx_data.size_bytes(), sizeof(T)); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive_DMA( @@ -1204,11 +1148,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(sizeof(T), rx_data.size_bytes()); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive_DMA( @@ -1231,11 +1171,7 @@ struct SPIDomain { spi_instance.operation_flag = operation_flag; auto size = std::min(sizeof(T1), sizeof(T2)); if (size % frame_size != 0) { - PANIC( - "SPI transaction size (%d) not aligned to frame size (%d)", - size, - frame_size - ); + PANIC("SPI transaction size (%d) not aligned to frame size (%d)", size, frame_size); return false; } auto error_code = HAL_SPI_TransmitReceive_DMA( diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index 284f1af7e..d81b6871f 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -378,11 +378,11 @@ struct TimerDomain { SET_BIT(RCC->b, RCC_##b##_TIM##n##EN); \ } - if (false) { - } - TimerXList else { - PANIC("Invalid timer given to rcc_enable_timer"); - } + if (false) { + } + TimerXList else { + PANIC("Invalid timer given to rcc_enable_timer"); + } #undef X } diff --git a/Inc/HALAL/Services/Communication/FDCAN/FDCAN.hpp b/Inc/HALAL/Services/Communication/FDCAN/FDCAN.hpp index f50027102..417781de7 100644 --- a/Inc/HALAL/Services/Communication/FDCAN/FDCAN.hpp +++ b/Inc/HALAL/Services/Communication/FDCAN/FDCAN.hpp @@ -144,10 +144,7 @@ class FDCAN { template uint8_t FDCAN::inscribe(FDCAN::Peripheral& fdcan) { if (!FDCAN::available_fdcans.contains(fdcan)) { - PANIC( - " The FDCAN peripheral %d is already used or does not exists.", - (uint16_t)fdcan - ); + PANIC(" The FDCAN peripheral %d is already used or does not exists.", (uint16_t)fdcan); return 0; } diff --git a/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp index cf1509986..596de6e91 100644 --- a/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp +++ b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp @@ -151,9 +151,7 @@ class Hub { if (sink_count >= Config::max_sinks) { return unexpected(RegistrationError::CAPACITY_EXCEEDED); } - if constexpr ( - sizeof(Sink) > Config::max_sink_storage || - alignof(Sink) > alignof(std::max_align_t)) { + if constexpr (sizeof(Sink) > Config::max_sink_storage || alignof(Sink) > alignof(std::max_align_t)) { return unexpected(RegistrationError::STORAGE_TOO_SMALL); } else { SinkStorage& slot = sink_storage[sink_count]; diff --git a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp index 22a9acd82..41f308484 100644 --- a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp +++ b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp @@ -6,32 +6,24 @@ class RuntimeDiagnosticReporter { public: - static void TriggerWarning( - const std::source_location& location, - const char* format, - ... - ); - static void TriggerInfo( - const std::source_location& location, - const char* format, - ... - ); + static void TriggerWarning(const std::source_location& location, const char* format, ...); + static void TriggerInfo(const std::source_location& location, const char* format, ...); static void Flush(); }; #define WARNING(x, ...) \ do { \ - RuntimeDiagnosticReporter::TriggerWarning( \ - std::source_location::current(), \ - x __VA_OPT__(, ) __VA_ARGS__ \ + RuntimeDiagnosticReporter::TriggerWarning( \ + std::source_location::current(), \ + x __VA_OPT__(, ) __VA_ARGS__ \ ); \ } while (0) #define INFO(x, ...) \ do { \ - RuntimeDiagnosticReporter::TriggerInfo( \ - std::source_location::current(), \ - x __VA_OPT__(, ) __VA_ARGS__ \ + RuntimeDiagnosticReporter::TriggerInfo( \ + std::source_location::current(), \ + x __VA_OPT__(, ) __VA_ARGS__ \ ); \ } while (0) diff --git a/Inc/HALAL/Services/PWM/DualPWM.hpp b/Inc/HALAL/Services/PWM/DualPWM.hpp index bd8f27ca9..b90c88c0e 100644 --- a/Inc/HALAL/Services/PWM/DualPWM.hpp +++ b/Inc/HALAL/Services/PWM/DualPWM.hpp @@ -70,9 +70,9 @@ class DualPWM { if (this->is_on_positive) return; - volatile HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim - ->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; + volatile HAL_TIM_ChannelStateTypeDef* state = + &timer->instance->hal_tim + ->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; if (*state != HAL_TIM_CHANNEL_STATE_READY) { PANIC("Channel not ready"); } @@ -104,9 +104,9 @@ class DualPWM { if (this->is_on_negative) return; - volatile HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim - ->ChannelNState[TimerDomain::get_channel_state_idx(negated_pin.channel)]; + volatile HAL_TIM_ChannelStateTypeDef* state = + &timer->instance->hal_tim + ->ChannelNState[TimerDomain::get_channel_state_idx(negated_pin.channel)]; if (*state != HAL_TIM_CHANNEL_STATE_READY) { PANIC("Channel not ready"); } diff --git a/Inc/HALAL/Services/PWM/PWM.hpp b/Inc/HALAL/Services/PWM/PWM.hpp index 05c1fe30e..56d82ca41 100644 --- a/Inc/HALAL/Services/PWM/PWM.hpp +++ b/Inc/HALAL/Services/PWM/PWM.hpp @@ -52,9 +52,9 @@ template class PWM { if (this->is_on) return; - volatile HAL_TIM_ChannelStateTypeDef* state = - &timer->instance->hal_tim - ->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; + volatile HAL_TIM_ChannelStateTypeDef* state = + &timer->instance->hal_tim + ->ChannelState[TimerDomain::get_channel_state_idx(pin.channel)]; if (*state != HAL_TIM_CHANNEL_STATE_READY) { PANIC("Channel not ready"); } diff --git a/Inc/HALAL/Services/Time/Scheduler.hpp b/Inc/HALAL/Services/Time/Scheduler.hpp index c7a508cd9..c37d2e53e 100644 --- a/Inc/HALAL/Services/Time/Scheduler.hpp +++ b/Inc/HALAL/Services/Time/Scheduler.hpp @@ -39,16 +39,16 @@ struct Scheduler { static constexpr uint32_t INVALID_ID = 2 * kMaxTasks; // temporary, will be removed - [[deprecated]] static inline void start() {} - static void update(); - static inline uint64_t get_global_tick() { - if (Scheduler_global_timer == nullptr) { - return global_tick_us_; - } - return global_tick_us_ + Scheduler_global_timer->CNT; - } - - static uint16_t register_task(uint32_t period_us, callback_t func); + [[deprecated]] static inline void start() {} + static void update(); + static inline uint64_t get_global_tick() { + if (Scheduler_global_timer == nullptr) { + return global_tick_us_; + } + return global_tick_us_ + Scheduler_global_timer->CNT; + } + + static uint16_t register_task(uint32_t period_us, callback_t func); static bool unregister_task(uint16_t id); static uint16_t set_timeout(uint32_t microseconds, callback_t func); diff --git a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp index 767caeaa6..91203e180 100644 --- a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp +++ b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp @@ -40,13 +40,8 @@ struct FaultCause { FaultRuntimePayload runtime{}; FaultProtectionPayload protection_event{}; - static FaultCause panic( - const char* message, - bool truncated, - int line, - const char* func, - const char* file - ); + static FaultCause + panic(const char* message, bool truncated, int line, const char* func, const char* file); static FaultCause runtime_fault( const char* message, bool truncated, @@ -66,8 +61,7 @@ class FaultController { using state_id = uint8_t; static constexpr size_t max_runtime_storage = 2048; - template - static void install_runtime() { + template static void install_runtime() { static_assert( requires { { Policy::has_operational_machine } -> std::convertible_to; @@ -127,8 +121,7 @@ class FaultController { } } - template - static void emplace_runtime_machine() { + template static void emplace_runtime_machine() { using RuntimeMachine = decltype(build_runtime_machine()); static_assert( sizeof(RuntimeMachine) <= max_runtime_storage, diff --git a/Inc/ST-LIB_HIGH/Protections/Protection.hpp b/Inc/ST-LIB_HIGH/Protections/Protection.hpp index 53cdc394e..2fd74281a 100644 --- a/Inc/ST-LIB_HIGH/Protections/Protection.hpp +++ b/Inc/ST-LIB_HIGH/Protections/Protection.hpp @@ -31,7 +31,9 @@ template constexpr bool is_above(T sample, T threshold) { return sa template constexpr bool is_equal_to(T lhs, T rhs) { return lhs == rhs; } -template constexpr bool is_not_equal_to(T lhs, T rhs) { return !is_equal_to(lhs, rhs); } +template constexpr bool is_not_equal_to(T lhs, T rhs) { + return !is_equal_to(lhs, rhs); +} } // namespace detail @@ -177,8 +179,7 @@ template struct RangeEvaluator { template struct EqualsEvaluator { static constexpr RuleState compute(const T& sample, const EqualsRuleConfig& config) { - return detail::is_equal_to(sample, config.expected) ? RuleState::FAULT - : RuleState::NORMAL; + return detail::is_equal_to(sample, config.expected) ? RuleState::FAULT : RuleState::NORMAL; } }; @@ -225,16 +226,17 @@ template struct TimeAccumulationEvaluator { active_time_s = static_cast(fault_active_time_us) / 1'000'000.0f; return RuleState::FAULT; } - if (config.warning_threshold.has_value() && warning_active_time_us >= configured_window_us) { + if (config.warning_threshold.has_value() && + warning_active_time_us >= configured_window_us) { active_time_s = static_cast(warning_active_time_us) / 1'000'000.0f; return RuleState::WARNING; } - active_time_s = static_cast( - config.warning_threshold.has_value() ? warning_active_time_us - : fault_active_time_us - ) / - 1'000'000.0f; + active_time_s = + static_cast( + config.warning_threshold.has_value() ? warning_active_time_us : fault_active_time_us + ) / + 1'000'000.0f; return RuleState::NORMAL; } }; @@ -298,8 +300,7 @@ template struct RangeRule { return { .state = state, .edge = edge, - .snapshot = - RuleSnapshotBuilder::range(sample, state, edge, previous_state, config), + .snapshot = RuleSnapshotBuilder::range(sample, state, edge, previous_state, config), }; } }; @@ -354,9 +355,9 @@ template struct NotEqualsRule { template struct TimeAccumulationRule { explicit TimeAccumulationRule(TimeAccumulationRuleConfig config) : config(config) { - configured_window_us = static_cast(std::llround( - static_cast(config.time_window_s) * 1'000'000.0 - )); + configured_window_us = static_cast( + std::llround(static_cast(config.time_window_s) * 1'000'000.0) + ); if (configured_window_us == 0) { configured_window_us = 1; } @@ -425,7 +426,8 @@ using RuleModel = variant< NotEqualsRule, TimeAccumulationRuleModel>; -template inline RuleModel make_rule_model(const RuleDefinition& definition) { +template +inline RuleModel make_rule_model(const RuleDefinition& definition) { return visit( [](const RuleConfig& config) -> RuleModel { using ConfigType = std::remove_cvref_t; @@ -453,7 +455,8 @@ template inline RuleModel make_rule_model(const RuleDefi ); } -template inline RuleEvaluation evaluate_rule(RuleModel& rule, const T& sample) { +template +inline RuleEvaluation evaluate_rule(RuleModel& rule, const T& sample) { return visit( [&sample](auto& concrete_rule) -> RuleEvaluation { using RuleType = std::remove_cvref_t; @@ -474,8 +477,8 @@ template class Protection { const char* get_name() const { return name; } void initialize() {} - expected - add_rule(expected, RuleConfigError> definition) { + expected add_rule(expected, RuleConfigError> definition + ) { if (!definition.has_value()) { return unexpected(ProtectionError::INVALID_RULE_CONFIGURATION); } diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionConcepts.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionConcepts.hpp index d19be3cc3..20ba92478 100644 --- a/Inc/ST-LIB_HIGH/Protections/ProtectionConcepts.hpp +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionConcepts.hpp @@ -4,14 +4,14 @@ namespace Protections { -template -using remove_cvref_t = std::remove_cvref_t; +template using remove_cvref_t = std::remove_cvref_t; template concept ArithmeticSample = std::is_arithmetic_v> && !std::same_as, long double>; -template concept FloatingSample = std::floating_point>; +template +concept FloatingSample = std::floating_point>; template concept ComparableSample = @@ -33,14 +33,14 @@ concept SupportedProtectionSample = std::same_as, uint64_t> || std::same_as, float> || std::same_as, double>; -template concept ProtectionSample = ArithmeticSample && SupportedProtectionSample; +template +concept ProtectionSample = ArithmeticSample && SupportedProtectionSample; template -concept ReadableSampleSource = - requires(const remove_cvref_t& source) { - typename remove_cvref_t::value_type; - requires ProtectionSample::value_type>; - { source.read() } -> std::convertible_to::value_type>; - }; +concept ReadableSampleSource = requires(const remove_cvref_t& source) { + typename remove_cvref_t::value_type; + requires ProtectionSample::value_type>; + { source.read() } -> std::convertible_to::value_type>; +}; } // namespace Protections diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp index c0cafe70f..9dea7a083 100644 --- a/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp @@ -14,8 +14,8 @@ template class ProtectionHandle { public: explicit ProtectionHandle(Protection* protection = nullptr) : protection(protection) {} - expected - add_rule(expected, RuleConfigError> definition) { + expected add_rule(expected, RuleConfigError> definition + ) { if (protection == nullptr) { return unexpected(ProtectionError::INVALID_HANDLE); } diff --git a/Inc/ST-LIB_HIGH/Protections/Rules.hpp b/Inc/ST-LIB_HIGH/Protections/Rules.hpp index 057e0b4a2..622c60a79 100644 --- a/Inc/ST-LIB_HIGH/Protections/Rules.hpp +++ b/Inc/ST-LIB_HIGH/Protections/Rules.hpp @@ -113,12 +113,8 @@ validate_above(optional warning_threshold, T fault_threshold) { } template -constexpr expected, RuleConfigError> validate_range( - T low_fault, - T high_fault, - optional low_warning, - optional high_warning -) { +constexpr expected, RuleConfigError> +validate_range(T low_fault, T high_fault, optional low_warning, optional high_warning) { const auto fault_validation = validate_with_consteval( [&] { return low_fault <= high_fault; }, RuleConfigError::INVALID_RANGE_THRESHOLDS, @@ -135,7 +131,8 @@ constexpr expected, RuleConfigError> validate_range( if (low_warning.has_value()) { const auto warning_validation = validate_with_consteval( [&] { - return low_fault <= low_warning.value() && low_warning.value() <= high_warning.value() && + return low_fault <= low_warning.value() && + low_warning.value() <= high_warning.value() && high_warning.value() <= high_fault; }, RuleConfigError::INVALID_RANGE_THRESHOLDS, @@ -155,11 +152,8 @@ constexpr expected, RuleConfigError> validate_range( } template -constexpr expected, RuleConfigError> validate_time_accumulation( - T fault_threshold, - optional warning_threshold, - float window_seconds -) { +constexpr expected, RuleConfigError> +validate_time_accumulation(T fault_threshold, optional warning_threshold, float window_seconds) { const auto window_validation = validate_with_consteval( [&] { return window_seconds > 0.0f; }, RuleConfigError::INVALID_WINDOW, @@ -235,20 +229,19 @@ constexpr expected, RuleConfigError> not_equals(T value) { } template -constexpr expected, RuleConfigError> time_accumulation( - T fault_threshold, - float window_seconds -) { +constexpr expected, RuleConfigError> +time_accumulation(T fault_threshold, float window_seconds) { return detail::validate_time_accumulation(fault_threshold, nullopt, window_seconds); } template -constexpr expected, RuleConfigError> time_accumulation( - T fault_threshold, - T warning_threshold, - float window_seconds -) { - return detail::validate_time_accumulation(fault_threshold, warning_threshold, window_seconds); +constexpr expected, RuleConfigError> +time_accumulation(T fault_threshold, T warning_threshold, float window_seconds) { + return detail::validate_time_accumulation( + fault_threshold, + warning_threshold, + window_seconds + ); } } // namespace Rules diff --git a/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp b/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp index de61fdd7a..b7954626f 100644 --- a/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp +++ b/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp @@ -10,32 +10,24 @@ class PanicReporter { public: - static void Trigger( - const std::source_location& location, - const char* format, - ... - ); + static void Trigger(const std::source_location& location, const char* format, ...); static void Flush(); }; class FaultReporter { public: - static void Trigger( - const std::source_location& location, - const char* format, - ... - ); + static void Trigger(const std::source_location& location, const char* format, ...); static void Flush(); }; #define PANIC(x, ...) \ do { \ - PanicReporter::Trigger(std::source_location::current(), x __VA_OPT__(, ) __VA_ARGS__); \ + PanicReporter::Trigger(std::source_location::current(), x __VA_OPT__(, ) __VA_ARGS__); \ } while (0) #define FAULT(x, ...) \ do { \ - FaultReporter::Trigger(std::source_location::current(), x __VA_OPT__(, ) __VA_ARGS__); \ + FaultReporter::Trigger(std::source_location::current(), x __VA_OPT__(, ) __VA_ARGS__); \ } while (0) using ErrorHandlerModel = PanicReporter; diff --git a/Inc/ST-LIB_LOW/Sd/Sd.hpp b/Inc/ST-LIB_LOW/Sd/Sd.hpp index c4b65c9d2..6f38bc045 100644 --- a/Inc/ST-LIB_LOW/Sd/Sd.hpp +++ b/Inc/ST-LIB_LOW/Sd/Sd.hpp @@ -509,7 +509,7 @@ struct SdDomain { if (translated_clock_div > 1023) { PANIC("SDMMC clock divider too high, cannot achieve target frequency " - "with current PLL1 Q clock"); + "with current PLL1 Q clock"); } inst.hsd.Init.ClockDiv = translated_clock_div; @@ -532,7 +532,7 @@ struct SdDomain { RCC_PeriphCLKInitStruct.SdmmcClockSelection = RCC_SDMMCCLKSOURCE_PLL; if (HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct) != HAL_OK) { PANIC("SDMMC clock configuration failed, maybe try with a slower clock or " - "higher divider?"); + "higher divider?"); } // Ensure PLL1Q output is enabled diff --git a/Src/HALAL/Models/PinModel/Pin.cpp b/Src/HALAL/Models/PinModel/Pin.cpp index a635a945c..2e74397f7 100644 --- a/Src/HALAL/Models/PinModel/Pin.cpp +++ b/Src/HALAL/Models/PinModel/Pin.cpp @@ -62,10 +62,7 @@ const string Pin::to_string() const { void Pin::inscribe(Pin& pin, OperationMode mode) { if (pin.mode != OperationMode::NOT_USED) { - PANIC( - "Pin %s is already registered, cannot register twice", - pin.to_string().c_str() - ); + PANIC("Pin %s is already registered, cannot register twice", pin.to_string().c_str()); return; } pin.mode = mode; diff --git a/Src/HALAL/Models/TimerDomain/TimerDomain.cpp b/Src/HALAL/Models/TimerDomain/TimerDomain.cpp index c8df1a895..370135ded 100644 --- a/Src/HALAL/Models/TimerDomain/TimerDomain.cpp +++ b/Src/HALAL/Models/TimerDomain/TimerDomain.cpp @@ -31,7 +31,7 @@ TimerDomain::InputCaptureInfo input_capture_info_dummy = { TimerDomain::InputCaptureInfo* TimerDomain::input_capture_info[max_instances] [input_capture_channels] = { - &input_capture_info_dummy + {&input_capture_info_dummy} }; TimerDomain::InputCaptureInfo TimerDomain::input_capture_info_backing[max_instances] [input_capture_channels]; diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp index bc23e42f7..f62d84385 100644 --- a/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp +++ b/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp @@ -167,11 +167,7 @@ void append_timestamp_suffix(const Timestamp& timestamp, char* buffer, size_t bu #endif } -void format_runtime_record( - const DiagnosticRecord& record, - char* buffer, - size_t buffer_size -) { +void format_runtime_record(const DiagnosticRecord& record, char* buffer, size_t buffer_size) { const RuntimeDiagnosticPayload& runtime = record.payload.runtime; append_formatted( buffer, @@ -188,11 +184,7 @@ void format_runtime_record( } } -void format_protection_record( - const DiagnosticRecord& record, - char* buffer, - size_t buffer_size -) { +void format_protection_record(const DiagnosticRecord& record, char* buffer, size_t buffer_size) { const ProtectionDiagnosticPayload& protection = record.payload.protection; char value_buffer[32]{}; char threshold_a_buffer[32]{}; diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp index 3550a526f..f87388a76 100644 --- a/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp +++ b/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp @@ -29,8 +29,7 @@ size_t bounded_strnlen(const char* src, size_t max_length) { return length; } -template -void copy_c_string(char (&dst)[Capacity], const char* src) { +template void copy_c_string(char (&dst)[Capacity], const char* src) { if (Capacity == 0) { return; } diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp index 549a606d2..3b0fdebf7 100644 --- a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp +++ b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp @@ -233,11 +233,9 @@ void Hub::publish_runtime_fault( const char* func, const char* file ) { - publish(RecordFactory::runtime_fault( - message, - truncated, - RuntimeSourceMetadata{line, func, file} - )); + publish( + RecordFactory::runtime_fault(message, truncated, RuntimeSourceMetadata{line, func, file}) + ); } void Hub::publish_runtime_panic( @@ -247,11 +245,9 @@ void Hub::publish_runtime_panic( const char* func, const char* file ) { - publish(RecordFactory::runtime_panic( - message, - truncated, - RuntimeSourceMetadata{line, func, file} - )); + publish( + RecordFactory::runtime_panic(message, truncated, RuntimeSourceMetadata{line, func, file}) + ); } void Hub::publish_runtime_warning( @@ -261,11 +257,9 @@ void Hub::publish_runtime_warning( const char* func, const char* file ) { - publish(RecordFactory::runtime_warning( - message, - truncated, - RuntimeSourceMetadata{line, func, file} - )); + publish( + RecordFactory::runtime_warning(message, truncated, RuntimeSourceMetadata{line, func, file}) + ); } void Hub::publish_runtime_info( @@ -275,11 +269,8 @@ void Hub::publish_runtime_info( const char* func, const char* file ) { - publish(RecordFactory::runtime_info( - message, - truncated, - RuntimeSourceMetadata{line, func, file} - )); + publish(RecordFactory::runtime_info(message, truncated, RuntimeSourceMetadata{line, func, file}) + ); } void Hub::publish_protection_event( @@ -292,8 +283,7 @@ void Hub::publish_protection_event( } void Hub::flush_pending(bool urgent_only) { - const uint8_t target_mask = - sink_count == 0 ? 0 : static_cast((1u << sink_count) - 1u); + const uint8_t target_mask = sink_count == 0 ? 0 : static_cast((1u << sink_count) - 1u); for (size_t record_index = 0; record_index < pending_count;) { PendingRecord& pending_record = pending_records[record_index]; diff --git a/Src/HALAL/Services/FMAC/FMAC.cpp b/Src/HALAL/Services/FMAC/FMAC.cpp index c78ff22f5..3940d1ea4 100644 --- a/Src/HALAL/Services/FMAC/FMAC.cpp +++ b/Src/HALAL/Services/FMAC/FMAC.cpp @@ -93,9 +93,7 @@ void MultiplierAccelerator::software_preload( #if FMAC_ERROR_CHECK != 0 if (amount_to_preload > MemoryLayout.FInSize || amount_to_preload > MemoryLayout.FeedbackInSize) { - PANIC( - "Error while preloading data, cannot preload more data than the structure can hold" - ); + PANIC("Error while preloading data, cannot preload more data than the structure can hold"); } while (HAL_FMAC_GetState(Instance.hfmac) != HAL_FMAC_STATE_READY) { diff --git a/Src/HALAL/Services/Time/Scheduler.cpp b/Src/HALAL/Services/Time/Scheduler.cpp index 6d90a9c4e..6dd370e85 100644 --- a/Src/HALAL/Services/Time/Scheduler.cpp +++ b/Src/HALAL/Services/Time/Scheduler.cpp @@ -58,13 +58,13 @@ void Scheduler_start(void) { ST_LIB::TimerDomain::callbacks[ST_LIB::timer_idxmap[static_cast(SCHEDULER_TIMER_DOMAIN )]] = Scheduler_global_timer_callback; - uint32_t prescaler_divisor = - ST_LIB::TimerDomain::get_timer_frequency(Scheduler_global_timer) / Scheduler::FREQUENCY; - if (prescaler_divisor == 0 || prescaler_divisor > (uint32_t)UINT16_MAX + 1u) { - PANIC("Invalid prescaler value: %u", prescaler_divisor); - return; - } - Scheduler_global_timer->PSC = static_cast(prescaler_divisor - 1u); + uint32_t prescaler_divisor = + ST_LIB::TimerDomain::get_timer_frequency(Scheduler_global_timer) / Scheduler::FREQUENCY; + if (prescaler_divisor == 0 || prescaler_divisor > (uint32_t)UINT16_MAX + 1u) { + PANIC("Invalid prescaler value: %u", prescaler_divisor); + return; + } + Scheduler_global_timer->PSC = static_cast(prescaler_divisor - 1u); Scheduler_global_timer->ARR = 0; Scheduler_global_timer->DIER |= LL_TIM_DIER_UIE; Scheduler_global_timer->CR1 = @@ -100,7 +100,7 @@ void Scheduler::update() { } } -inline uint8_t Scheduler::allocate_slot() { +inline uint8_t Scheduler::allocate_slot() { uint32_t idx = __builtin_ffs(Scheduler::free_bitmap_) - 1; if (idx >= Scheduler::kMaxTasks) [[unlikely]] return static_cast(Scheduler::INVALID_ID); diff --git a/Src/ST-LIB.cpp b/Src/ST-LIB.cpp index f14bad9b8..2f93ddf99 100644 --- a/Src/ST-LIB.cpp +++ b/Src/ST-LIB.cpp @@ -25,9 +25,7 @@ void STLIB::start( #else // !STLIB_ETH -void STLIB::start(UART::Peripheral& printf_peripheral) { - HALAL::start(printf_peripheral); -} +void STLIB::start(UART::Peripheral& printf_peripheral) { HALAL::start(printf_peripheral); } #endif // STLIB_ETH diff --git a/Src/ST-LIB_HIGH/Protections/FaultController.cpp b/Src/ST-LIB_HIGH/Protections/FaultController.cpp index ca8b1689a..1d908c0f9 100644 --- a/Src/ST-LIB_HIGH/Protections/FaultController.cpp +++ b/Src/ST-LIB_HIGH/Protections/FaultController.cpp @@ -5,7 +5,9 @@ namespace { static_assert(FaultConfig::origin_capacity == Diagnostics::Config::origin_capacity); -static_assert(FaultConfig::runtime_message_capacity == Diagnostics::Config::runtime_message_capacity); +static_assert( + FaultConfig::runtime_message_capacity == Diagnostics::Config::runtime_message_capacity +); static_assert(FaultConfig::function_capacity == Diagnostics::Config::function_capacity); static_assert(FaultConfig::file_capacity == Diagnostics::Config::file_capacity); diff --git a/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp b/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp index 5d9b520b2..8051c6db4 100644 --- a/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp +++ b/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp @@ -16,7 +16,10 @@ void ProtectionEngine::initialize() { registration_locked = true; for (size_t protection_index = 0; protection_index < protection_count; protection_index++) { if (protections[protection_index].has_value()) { - visit([](auto& protection) { protection.initialize(); }, *protections[protection_index]); + visit( + [](auto& protection) { protection.initialize(); }, + *protections[protection_index] + ); } } } diff --git a/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp b/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp index daa2ffa80..c73468cbc 100644 --- a/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp +++ b/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp @@ -7,6 +7,4 @@ #include "ST-LIB_HIGH.hpp" -void STLIB_HIGH::start() { - ProtectionEngine::initialize(); -} +void STLIB_HIGH::start() { ProtectionEngine::initialize(); } diff --git a/Tests/TestAccess.hpp b/Tests/TestAccess.hpp index de2f05011..8abf32400 100644 --- a/Tests/TestAccess.hpp +++ b/Tests/TestAccess.hpp @@ -41,7 +41,9 @@ struct ProtectionEngine { }; struct FaultController { - static void request_fault(const ::FaultCause& cause) { ::FaultController::request_fault(cause); } + static void request_fault(const ::FaultCause& cause) { + ::FaultController::request_fault(cause); + } }; } // namespace ST_LIB::TestAccess diff --git a/Tests/diagnostics_test.cpp b/Tests/diagnostics_test.cpp index 7c814d578..e7c0ef176 100644 --- a/Tests/diagnostics_test.cpp +++ b/Tests/diagnostics_test.cpp @@ -14,7 +14,7 @@ namespace ST_LIB::TestPanicReporter { void set_fail_on_error(bool enabled); void reset(); -} +} // namespace ST_LIB::TestPanicReporter namespace { @@ -55,12 +55,13 @@ size_t operational_hold_enter_count = 0; static constexpr auto operational_run_state = make_state(OperationalState::RUN, Transition{OperationalState::HOLD, []() { - return transition_to_hold; - }}); + return transition_to_hold; + }}); static constexpr auto operational_hold_state = make_state(OperationalState::HOLD); static inline auto test_operational_machine = []() consteval { - auto sm = make_state_machine(OperationalState::RUN, operational_run_state, operational_hold_state); + auto sm = + make_state_machine(OperationalState::RUN, operational_run_state, operational_hold_state); sm.add_enter_action([]() { operational_hold_enter_count++; }, operational_hold_state); return sm; }(); @@ -262,14 +263,8 @@ TEST_F(DiagnosticsHubTest, ProtectionEngineEvaluatesRulesAndPublishesSnapshots) EXPECT_EQ(sink->records.front().category, Diagnostics::Category::PROTECTION_EVENT); EXPECT_EQ(sink->records.front().severity, Diagnostics::Severity::FAULT); EXPECT_EQ(sink->records.front().priority, Diagnostics::DiagnosticPriority::URGENT); - EXPECT_EQ( - sink->records.front().payload.protection.state, - Protections::RuleState::FAULT - ); - EXPECT_EQ( - sink->records.front().payload.protection.rule_kind, - Protections::RuleKind::BELOW - ); + EXPECT_EQ(sink->records.front().payload.protection.state, Protections::RuleState::FAULT); + EXPECT_EQ(sink->records.front().payload.protection.rule_kind, Protections::RuleKind::BELOW); } TEST_F(DiagnosticsHubTest, TimeAccumulationUsesSchedulerTickForContinuousDuration) { @@ -282,7 +277,9 @@ TEST_F(DiagnosticsHubTest, TimeAccumulationUsesSchedulerTickForContinuousDuratio auto protection = ProtectionEngine::create_protection("time_value", source); ASSERT_TRUE(protection.has_value()); - ASSERT_TRUE(protection->add_rule(Protections::Rules::time_accumulation(10.0f, 0.001f)).has_value()); + ASSERT_TRUE( + protection->add_rule(Protections::Rules::time_accumulation(10.0f, 0.001f)).has_value() + ); ProtectionEngine::initialize(); @@ -302,7 +299,10 @@ TEST_F(DiagnosticsHubTest, TimeAccumulationUsesSchedulerTickForContinuousDuratio ASSERT_FALSE(sink->records.empty()); EXPECT_TRUE(FaultController::is_faulted()); EXPECT_EQ(sink->records.back().category, Diagnostics::Category::PROTECTION_EVENT); - EXPECT_EQ(sink->records.back().payload.protection.rule_kind, Protections::RuleKind::TIME_ACCUMULATION); + EXPECT_EQ( + sink->records.back().payload.protection.rule_kind, + Protections::RuleKind::TIME_ACCUMULATION + ); EXPECT_FLOAT_EQ(sink->records.back().payload.protection.time_window_s, 0.001f); EXPECT_FLOAT_EQ(sink->records.back().payload.protection.active_time_s, 0.001f); } @@ -313,7 +313,9 @@ TEST_F(DiagnosticsHubTest, TimeAccumulationResetsWhenConditionClears) { auto protection = ProtectionEngine::create_protection("time_reset_value", source); ASSERT_TRUE(protection.has_value()); - ASSERT_TRUE(protection->add_rule(Protections::Rules::time_accumulation(10.0f, 0.001f)).has_value()); + ASSERT_TRUE( + protection->add_rule(Protections::Rules::time_accumulation(10.0f, 0.001f)).has_value() + ); ProtectionEngine::initialize(); @@ -417,10 +419,7 @@ TEST_F(DiagnosticsHubTest, RuntimeDiagnosticsCaptureCallerSourceLocation) { ASSERT_FALSE(sink->records.empty()); EXPECT_EQ(sink->records.front().payload.runtime.line, expected_line); EXPECT_NE( - strstr( - sink->records.front().payload.runtime.function_name, - "emit_warning_and_return_line" - ), + strstr(sink->records.front().payload.runtime.function_name, "emit_warning_and_return_line"), nullptr ); } From a4a20705ca8fab5252b8f111cd313090e8bb2360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 22 Apr 2026 21:01:50 +0200 Subject: [PATCH 14/23] added changeset --- .changesets/pm-no-eth-major.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .changesets/pm-no-eth-major.md diff --git a/.changesets/pm-no-eth-major.md b/.changesets/pm-no-eth-major.md new file mode 100644 index 000000000..e7a11f6d2 --- /dev/null +++ b/.changesets/pm-no-eth-major.md @@ -0,0 +1,21 @@ +release: major +summary: Redesign fault handling, protections, and Board bootstrap around explicit fault policies + +This PR changes the public integration contract for applications built on ST-LIB. + +Breaking changes: + +- `Board` now takes the fault policy type as its first template parameter. +- The global `FAULT` runtime is owned exclusively by `FaultController`. +- User state machines are now nested under the global `OPERATIONAL` state through `FaultPolicy` or `FaultPolicyNoMachine`. +- Protections now use `ProtectionEngine` and `Protections::Rules::*`; the previous `ProtectionManager` and boundary split is no longer the active model. +- Runtime reporting is unified under `PANIC(...)`, `FAULT(...)`, `WARNING(...)`, and `INFO(...)`. +- The real bootstrap path is `Board::init()`. Legacy `STLIB::start()`, `STLIB::update()`, `STLIB_LOW::start()`, and `STLIB_HIGH::start()` must not be used as the integration path. + +Migration notes: + +- Declare the board as `Board`. +- Use `FaultPolicy` when you want an operational state machine nested under the global runtime. +- Use `FaultPolicyNoMachine` when you only need a fault-entry callback. +- Use `DefaultFaultPolicy` when you want neither an operational machine nor a fault-entry callback. +- In the main loop, drive the runtime through `FaultController::check_transitions()`, `ProtectionEngine::evaluate()`, and `Diagnostics::Hub::flush()`. From 89cd0e88a386626bc7a8ba37fa154028568570da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 22 Apr 2026 21:35:35 +0200 Subject: [PATCH 15/23] Remove legacy bootstrap entry points and compatibility shims --- CMakeLists.txt | 5 - Inc/HALAL/HALAL.hpp | 10 - Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 38 ++-- .../TimerPeripheral/TimerPeripheral.hpp.old | 89 --------- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 12 +- Inc/HALAL/Services/FMAC/FMAC.hpp | 2 +- .../Services/InfoWarning/InfoWarning.hpp | 2 - .../Services/InputCapture/InputCapture.hpp | 4 +- .../InputCapture/InputCapture.hpp.old | 47 ----- .../PWM/DualPhasedPWM/DualPhasedPWM.hpp.old | 25 --- .../Services/PWM/PhasedPWM/PhasedPWM.hpp.old | 47 ----- Inc/ST-LIB.hpp | 22 --- Inc/ST-LIB_HIGH/ST-LIB_HIGH.hpp | 3 - Inc/ST-LIB_LOW/Clocks/Stopwatch.hpp.old | 20 -- Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp | 5 - Inc/ST-LIB_LOW/HalfBridge/HalfBridge.hpp.old | 42 ----- Inc/ST-LIB_LOW/ST-LIB_LOW.hpp | 5 - .../Sensors/PWMSensor/PWMSensor.hpp.old | 47 ----- Inc/ST-LIB_LOW/Sensors/Sensor/Sensor.hpp.old | 16 -- Src/HALAL/HALAL.cpp | 91 --------- .../TimerPeripheral/TimerPeripheral.cpp.old | 174 ------------------ .../InputCapture/InputCapture.cpp.old | 147 --------------- .../PWM/DualPhasedPWM/DualPhasedPWM.cpp.old | 108 ----------- .../Services/PWM/PhasedPWM/PhasedPWM.cpp.old | 126 ------------- Src/ST-LIB.cpp | 46 ----- Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp | 10 - Src/ST-LIB_LOW/Clocks/Stopwatch.cpp.old | 23 --- Src/ST-LIB_LOW/HalfBridge/HalfBridge.cpp.old | 65 ------- Src/ST-LIB_LOW/ST-LIB_LOW.cpp | 12 -- Src/ST-LIB_LOW/Sensors/Sensor/Sensor.cpp.old | 12 -- 30 files changed, 28 insertions(+), 1227 deletions(-) delete mode 100644 Inc/HALAL/Models/TimerPeripheral/TimerPeripheral.hpp.old delete mode 100644 Inc/HALAL/Services/InputCapture/InputCapture.hpp.old delete mode 100644 Inc/HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.hpp.old delete mode 100644 Inc/HALAL/Services/PWM/PhasedPWM/PhasedPWM.hpp.old delete mode 100644 Inc/ST-LIB_LOW/Clocks/Stopwatch.hpp.old delete mode 100644 Inc/ST-LIB_LOW/HalfBridge/HalfBridge.hpp.old delete mode 100644 Inc/ST-LIB_LOW/Sensors/PWMSensor/PWMSensor.hpp.old delete mode 100644 Inc/ST-LIB_LOW/Sensors/Sensor/Sensor.hpp.old delete mode 100644 Src/HALAL/Models/TimerPeripheral/TimerPeripheral.cpp.old delete mode 100644 Src/HALAL/Services/InputCapture/InputCapture.cpp.old delete mode 100644 Src/HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.cpp.old delete mode 100644 Src/HALAL/Services/PWM/PhasedPWM/PhasedPWM.cpp.old delete mode 100644 Src/ST-LIB.cpp delete mode 100644 Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp delete mode 100644 Src/ST-LIB_LOW/Clocks/Stopwatch.cpp.old delete mode 100644 Src/ST-LIB_LOW/HalfBridge/HalfBridge.cpp.old delete mode 100644 Src/ST-LIB_LOW/ST-LIB_LOW.cpp delete mode 100644 Src/ST-LIB_LOW/Sensors/Sensor/Sensor.cpp.old diff --git a/CMakeLists.txt b/CMakeLists.txt index af28973e0..f83042976 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -372,7 +372,6 @@ set(STLIB_LOW_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/DigitalOutput/DigitalOutput.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Math/Math.cpp - ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/ST-LIB_LOW.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sd/Sd.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sensors/DigitalSensor/DigitalSensor.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_LOW/Sensors/SensorInterrupt/SensorInterrupt.cpp @@ -393,7 +392,6 @@ set(STLIB_HIGH_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashVariable.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/FaultController.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp - ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp ) # ============================ @@ -429,9 +427,6 @@ add_library(${STLIB_LIBRARY} OBJECT $<$:${STLIB_HIGH_CPP_NO_ETH}> $<$,$>:${STLIB_HIGH_C_ETH}> $<$,$>:${STLIB_HIGH_CPP_ETH}> - - $<$:${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB.cpp> - $<$>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Services/Time/Scheduler.cpp> $<$>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/TimerDomain/TimerDomain.cpp> $<$>:${CMAKE_CURRENT_LIST_DIR}/Src/HALAL/Models/MPUManager/MPUManager.cpp> diff --git a/Inc/HALAL/HALAL.hpp b/Inc/HALAL/HALAL.hpp index e8cec2fdb..9c3cf5fca 100644 --- a/Inc/HALAL/HALAL.hpp +++ b/Inc/HALAL/HALAL.hpp @@ -68,13 +68,3 @@ #include "HALAL/Services/Communication/Ethernet/LWIP/UDP/DatagramSocket.hpp" #include "HALAL/Services/Communication/SNTP/SNTP.hpp" #endif - -namespace HALAL { - -#ifdef STLIB_ETH -void start(MAC mac, IPV4 ip, IPV4 subnet_mask, IPV4 gateway, UART::Peripheral& printf_peripheral); -#else -void start(UART::Peripheral& printf_peripheral); -#endif - -} // namespace HALAL diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index d81b6871f..cc73df75d 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -806,13 +806,13 @@ struct TimerDomain { sMasterConfig.MasterOutputTrigger = static_cast(e.trgo1); sMasterConfig.MasterOutputTrigger2 = static_cast(e.trgo2); sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; - inst->master = sMasterConfig; - if (HAL_TIMEx_MasterConfigSynchronization(inst->hal_tim, &sMasterConfig) != - HAL_OK) { - ErrorHandler("Unable to configure master synch"); - } - } - } + inst->master = sMasterConfig; + if (HAL_TIMEx_MasterConfigSynchronization(inst->hal_tim, &sMasterConfig) != + HAL_OK) { + PANIC("Unable to configure master synch"); + } + } + } }; static consteval uint8_t get_channel_state_idx(const ST_LIB::TimerChannel ch) { @@ -871,18 +871,18 @@ struct TimerDomain { if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) != RCC_HCLK_DIV1) { result *= 2; } - } else if ((tim == TIM1) || (tim == TIM8) || (tim == TIM15) || (tim == TIM16) || (tim == TIM17) || (tim == TIM23) || (tim == TIM24)) { - result = HAL_RCC_GetPCLK2Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { - result *= 2; - } - } else { - ErrorHandler("Invalid timer ptr"); - } - - return result; - } -}; + } else if ((tim == TIM1) || (tim == TIM8) || (tim == TIM15) || (tim == TIM16) || (tim == TIM17) || (tim == TIM23) || (tim == TIM24)) { + result = HAL_RCC_GetPCLK2Freq(); + if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { + result *= 2; + } + } else { + PANIC("Invalid timer ptr"); + } + + return result; + } +}; consteval GPIODomain::AlternateFunction TimerDomain::Timer::get_gpio_af(ST_LIB::TimerRequest req, ST_LIB::TimerPin pin) { diff --git a/Inc/HALAL/Models/TimerPeripheral/TimerPeripheral.hpp.old b/Inc/HALAL/Models/TimerPeripheral/TimerPeripheral.hpp.old deleted file mode 100644 index 3cfb047c8..000000000 --- a/Inc/HALAL/Models/TimerPeripheral/TimerPeripheral.hpp.old +++ /dev/null @@ -1,89 +0,0 @@ -/* - * TimerPeripheral.hpp - * - * Created on: 3 dic. 2022 - * Author: aleja - */ - -#pragma once -#include "stm32h7xx_hal.h" - -#ifdef HAL_TIM_MODULE_ENABLED - -extern TIM_HandleTypeDef htim1; -extern TIM_HandleTypeDef htim3; -extern TIM_HandleTypeDef htim4; -extern TIM_HandleTypeDef htim8; -extern TIM_HandleTypeDef htim12; -extern TIM_HandleTypeDef htim15; -extern TIM_HandleTypeDef htim16; -extern TIM_HandleTypeDef htim17; -extern TIM_HandleTypeDef htim23; - -#include "C++Utilities/CppUtils.hpp" -#include "ErrorHandler/ErrorHandler.hpp" - -#define PWMmap map, TimerPeripheral::PWMData>> -#define DualPWMmap \ - map, pair, TimerPeripheral::PWMData>> - -class TimerPeripheral { -public: - enum PWM_MODE : uint8_t { NORMAL = 0, PHASED = 1, CENTER_ALIGNED = 2 }; - - enum TIM_TYPE { BASE, ADVANCED }; - - struct PWMData { - uint32_t channel; - PWM_MODE mode; - }; - - struct InitData { - private: - InitData() = default; - - public: - TIM_TYPE type; - uint32_t prescaler; - uint32_t period; - uint32_t deadtime; - uint32_t polarity; - uint32_t negated_polarity; - vector pwm_channels = {}; - vector> input_capture_channels = {}; - InitData( - TIM_TYPE type, - uint32_t prescaler = 5, - uint32_t period = 55000, - uint32_t deadtime = 0, - uint32_t polarity = TIM_OCPOLARITY_HIGH, - uint32_t negated_polarity = TIM_OCPOLARITY_HIGH - ); - }; - - TIM_HandleTypeDef* handle; - InitData init_data; - string name; - - static PWMmap available_pwm; - static DualPWMmap available_dual_pwms; - static vector> timers; - - TimerPeripheral() = default; - TimerPeripheral(TIM_HandleTypeDef* timer, InitData&& init_data, string name); - - static void start(); - - void init(); - bool is_registered(); - uint32_t get_prescaler(); - uint32_t get_period(); - bool is_occupied(); - -private: - static map handle_to_timer; - - friend class Encoder; -}; - -#endif diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index c0f8c3bc1..fc0ea68c0 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -728,12 +728,12 @@ struct DFSDM_CHANNEL_DOMAIN { }; static void inline start_reg_conv_filter(uint8_t filter) { if (filter > 3) - ErrorHandler("Only filters from 0..3"); + PANIC("Only filters from 0..3"); filter_hw[filter]->FLTCR1 |= DFSDM_FLTCR1_RSWSTART; // regular } static void inline start_inj_conv_filter(uint8_t filter) { if (filter > 3) - ErrorHandler("Only filters from 0..3"); + PANIC("Only filters from 0..3"); filter_hw[filter]->FLTCR1 |= DFSDM_FLTCR1_JSWSTART; // injected } static DMADomain::Instance* @@ -897,8 +897,8 @@ struct DFSDM_CHANNEL_DOMAIN { } int32_t read(size_t pos) { if (pos >= this->length_buffer) { - ErrorHandler("DFSDM: Trying to access to a memory section that is not from the " - "channel buffer"); + PANIC("DFSDM: Trying to access to a memory section that is not from the channel " + "buffer"); } return ( static_cast(this->buffer[pos] & DFSDM_FLTJDATAR_JDATA_Msk) >> @@ -1014,7 +1014,7 @@ struct DFSDM_CHANNEL_DOMAIN { case 3: return sizes.filter3; } - ErrorHandler("Filter cannot be bigger than 3"); + PANIC("Filter cannot be bigger than 3"); return 0; } static int32_t* get_buffer_filter(uint8_t filter) { @@ -1028,7 +1028,7 @@ struct DFSDM_CHANNEL_DOMAIN { case 3: return Buffer_Filter3; } - ErrorHandler("Filter cannot be bigger than 3"); + PANIC("Filter cannot be bigger than 3"); return 0; } diff --git a/Inc/HALAL/Services/FMAC/FMAC.hpp b/Inc/HALAL/Services/FMAC/FMAC.hpp index 403fe8371..ba2646bb0 100644 --- a/Inc/HALAL/Services/FMAC/FMAC.hpp +++ b/Inc/HALAL/Services/FMAC/FMAC.hpp @@ -76,7 +76,7 @@ class MultiplierAccelerator { ); /** - * @brief used in the HALAL::start() to end the configuration of the FMAC. + * @brief Finalizes FMAC peripheral configuration during board bootstrap. */ static void start(); diff --git a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp index 41f308484..6ee33ae45 100644 --- a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp +++ b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp @@ -26,5 +26,3 @@ class RuntimeDiagnosticReporter { x __VA_OPT__(, ) __VA_ARGS__ \ ); \ } while (0) - -using InfoWarning = RuntimeDiagnosticReporter; diff --git a/Inc/HALAL/Services/InputCapture/InputCapture.hpp b/Inc/HALAL/Services/InputCapture/InputCapture.hpp index d031c9c87..8dc30911e 100644 --- a/Inc/HALAL/Services/InputCapture/InputCapture.hpp +++ b/Inc/HALAL/Services/InputCapture/InputCapture.hpp @@ -86,7 +86,7 @@ class InputCapture { ->ChannelNState[TimerDomain::get_channel_state_idx(pin_rising.channel)]; if ((*chx_1_state != HAL_TIM_CHANNEL_STATE_READY) || (*chx_1_n_state != HAL_TIM_CHANNEL_STATE_READY)) { - ErrorHandler("Channels not ready"); + PANIC("Channels not ready"); return; } @@ -110,7 +110,7 @@ class InputCapture { ->ChannelNState[TimerDomain::get_channel_state_idx(channel_falling)]; if ((*ch_state != HAL_TIM_CHANNEL_STATE_READY) || (*n_ch_state != HAL_TIM_CHANNEL_STATE_READY)) [[unlikely]] { - ErrorHandler("Channels not ready"); + PANIC("Channels not ready"); timer->template disable_capture_compare_interrupt(); CLEAR_BIT(timer->instance->tim->CCER, enableCCx_1); diff --git a/Inc/HALAL/Services/InputCapture/InputCapture.hpp.old b/Inc/HALAL/Services/InputCapture/InputCapture.hpp.old deleted file mode 100644 index 4de4cd25f..000000000 --- a/Inc/HALAL/Services/InputCapture/InputCapture.hpp.old +++ /dev/null @@ -1,47 +0,0 @@ -/* - * InputCapture.hpp - * - * Created on: 30 oct. 2022 - * Author: alejandro - */ - -#pragma once -#include "HALAL/Models/PinModel/Pin.hpp" -#include "HALAL/Models/TimerPeripheral/TimerPeripheral.hpp" - -#ifdef HAL_TIM_MODULE_ENABLED - -class InputCapture { -public: - class Instance { - public: - uint8_t id; - Pin pin; - TimerPeripheral* peripheral; - uint32_t channel_rising; - uint32_t channel_falling; - uint32_t frequency; - uint8_t duty_cycle; - - Instance() = default; - Instance( - Pin& pin, - TimerPeripheral* peripheral, - uint32_t channel_rising, - uint32_t channel_falling - ); - }; - - static map active_instances; - static map available_instances; - static uint8_t id_counter; - - static uint8_t inscribe(Pin& pin); - static void turn_on(uint8_t id); - static void turn_off(uint8_t id); - static uint32_t read_frequency(uint8_t id); - static uint8_t read_duty_cycle(uint8_t id); - static Instance find_instance_by_channel(uint32_t channel); -}; - -#endif diff --git a/Inc/HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.hpp.old b/Inc/HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.hpp.old deleted file mode 100644 index 0962ef100..000000000 --- a/Inc/HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.hpp.old +++ /dev/null @@ -1,25 +0,0 @@ -/* - * DualPhasedPWMInstance.hpp - * - * Created on: Feb 27, 2023 - * Author: aleja - */ - -#pragma once - -#include "HALAL/Services/PWM/PhasedPWM/PhasedPWM.hpp" -#include "HALAL/Services/PWM/DualPWM/DualPWM.hpp" - -class DualPhasedPWM : public DualPWM { -private: - float raw_phase{}; - -public: - DualPhasedPWM() = default; - DualPhasedPWM(Pin& pin, Pin& pin_negated); - void set_duty_cycle(float duty_cycle); - void set_frequency(uint32_t frequency); - void set_raw_phase(float raw_phase); - void set_phase(float phase_in_deg); - float get_phase() const; -}; diff --git a/Inc/HALAL/Services/PWM/PhasedPWM/PhasedPWM.hpp.old b/Inc/HALAL/Services/PWM/PhasedPWM/PhasedPWM.hpp.old deleted file mode 100644 index 46286081a..000000000 --- a/Inc/HALAL/Services/PWM/PhasedPWM/PhasedPWM.hpp.old +++ /dev/null @@ -1,47 +0,0 @@ -/* - * PhasedPWMInstance.hpp - * - * Created on: Feb 27, 2023 - * Author: aleja - */ - -#pragma once - -#include "HALAL/Services/PWM/PWM/PWM.hpp" - -#define STLIB_TIMER_CCMR_PWM_MODE_1 0x0 -#define STLIB_TIMER_CCMR_PWM_MODE_2 (16 + 4096) -#define STLIB_TIMER_CCMR_REGISTER_MODE_MASK (0xFFFFFFFF - STLIB_TIMER_CCMR_PWM_MODE_2) - -#define __STLIB_TIM_SET_MODE(__HANDLE__, __CHANNEL__, __CCMR_PWM_MODE_COMPARE__) \ - switch (__CHANNEL__) { \ - case TIM_CHANNEL_1: \ - case TIM_CHANNEL_2: \ - (__HANDLE__)->Instance->CCMR1 &= STLIB_TIMER_CCMR_REGISTER_MODE_MASK; \ - (__HANDLE__)->Instance->CCMR1 |= __CCMR_PWM_MODE_COMPARE__; \ - break; \ - case TIM_CHANNEL_3: \ - case TIM_CHANNEL_4: \ - (__HANDLE__)->Instance->CCMR2 &= STLIB_TIMER_CCMR_REGISTER_MODE_MASK; \ - (__HANDLE__)->Instance->CCMR2 |= __CCMR_PWM_MODE_COMPARE__; \ - break; \ - case TIM_CHANNEL_5: \ - default: \ - (__HANDLE__)->Instance->CCMR3 &= STLIB_TIMER_CCMR_REGISTER_MODE_MASK; \ - (__HANDLE__)->Instance->CCMR3 |= __CCMR_PWM_MODE_COMPARE__; \ - break; \ - } - -class PhasedPWM : public PWM { -protected: - float raw_phase{}; - PhasedPWM() = default; - -public: - void set_duty_cycle(float duty_cycle); - void set_frequency(uint32_t frequency); - void set_raw_phase(float phase); - void set_phase(float phase_in_deg); - float get_phase() const; - PhasedPWM(Pin& pin); -}; diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index fad4a1e33..a518d269e 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -1,32 +1,10 @@ #pragma once -#include - #include "HALAL/Services/Diagnostics/Diagnostics.hpp" #include "HALAL/HALAL.hpp" #include "ST-LIB_HIGH.hpp" #include "ST-LIB_LOW.hpp" -class STLIB { -public: -#ifdef STLIB_ETH - static void - start(MAC mac, IPV4 ip, IPV4 subnet_mask, IPV4 gateway, UART::Peripheral& printf_peripheral); - - static void start( - const std::string& mac = "00:80:e1:00:00:00", - const std::string& ip = "192.168.1.4", - const std::string& subnet_mask = "255.255.0.0", - const std::string& gateway = "192.168.1.1", - UART::Peripheral& printf_peripheral = UART::uart2 - ); -#else - static void start(UART::Peripheral& printf_peripheral = UART::uart2); -#endif - - static void update(); -}; - namespace ST_LIB { extern void compile_error(const char* msg); diff --git a/Inc/ST-LIB_HIGH/ST-LIB_HIGH.hpp b/Inc/ST-LIB_HIGH/ST-LIB_HIGH.hpp index 3b7c5609e..737096384 100644 --- a/Inc/ST-LIB_HIGH/ST-LIB_HIGH.hpp +++ b/Inc/ST-LIB_HIGH/ST-LIB_HIGH.hpp @@ -28,6 +28,3 @@ #else #include "FlashStorer/FlashStorer.hpp" #endif -namespace STLIB_HIGH { -void start(); -} diff --git a/Inc/ST-LIB_LOW/Clocks/Stopwatch.hpp.old b/Inc/ST-LIB_LOW/Clocks/Stopwatch.hpp.old deleted file mode 100644 index 18ae527aa..000000000 --- a/Inc/ST-LIB_LOW/Clocks/Stopwatch.hpp.old +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Time.hpp - * - * Created on: 11 nov. 2022 - * Author: Dani - */ - -#pragma once -#include "C++Utilities/CppUtils.hpp" -#include "ErrorHandler/ErrorHandler.hpp" - -class Stopwatch { - -private: - map start_times; - -public: - void start(const string); - uint64_t stop(const string); -}; diff --git a/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp b/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp index b7954626f..3bac21830 100644 --- a/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp +++ b/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp @@ -29,8 +29,3 @@ class FaultReporter { do { \ FaultReporter::Trigger(std::source_location::current(), x __VA_OPT__(, ) __VA_ARGS__); \ } while (0) - -using ErrorHandlerModel = PanicReporter; - -// Deprecated compatibility macro. -#define ErrorHandler(x, ...) PANIC(x __VA_OPT__(, ) __VA_ARGS__) diff --git a/Inc/ST-LIB_LOW/HalfBridge/HalfBridge.hpp.old b/Inc/ST-LIB_LOW/HalfBridge/HalfBridge.hpp.old deleted file mode 100644 index 37f34d75b..000000000 --- a/Inc/ST-LIB_LOW/HalfBridge/HalfBridge.hpp.old +++ /dev/null @@ -1,42 +0,0 @@ -/* - * HalfBridge.hpp - * - * Created on: Dec 1, 2022 - * Author: aleja - */ - -#pragma once - -#include - -#include "HALAL/Models/PinModel/Pin.hpp" -#include "HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.hpp" -#include "ErrorHandler/ErrorHandler.hpp" - -class HalfBridge { -public: - HalfBridge() = default; - HalfBridge( - Pin& positive_pwm_pin, - Pin& positive_pwm_negated_pin, - Pin& negative_pwm_pin, - Pin& negative_pwm_negated_pin, - Pin& enable_pin - ); - - void turn_on(); - void turn_off(); - void set_duty_cycle(float duty_cycle); - void set_frequency(int32_t frequency); - void set_phase(float phase); - void set_positive_pwm_phase(float phase); - void set_negative_pwm_phase(float phase); - float get_phase(); - -private: - bool is_dual; - DualPhasedPWM positive_pwm; - DualPhasedPWM negative_pwm; - - uint8_t enable; -}; diff --git a/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp b/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp index 68c1f7163..1173a3e59 100644 --- a/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp +++ b/Inc/ST-LIB_LOW/ST-LIB_LOW.hpp @@ -28,8 +28,3 @@ #include "StateMachine/HeapStateOrder.hpp" #include "StateMachine/StackStateOrder.hpp" #endif - -class STLIB_LOW { -public: - static void start(); -}; diff --git a/Inc/ST-LIB_LOW/Sensors/PWMSensor/PWMSensor.hpp.old b/Inc/ST-LIB_LOW/Sensors/PWMSensor/PWMSensor.hpp.old deleted file mode 100644 index 4e6c00ce0..000000000 --- a/Inc/ST-LIB_LOW/Sensors/PWMSensor/PWMSensor.hpp.old +++ /dev/null @@ -1,47 +0,0 @@ - -/* - * SensorInterrupt.hpp - * - * Created on: June 21, 2023 - * Author: Alejandro - */ - -#pragma once -#include - -#include "HALAL/Models/PinModel/Pin.hpp" -#include "HALAL/Services/InputCapture/InputCapture.hpp" -#include "Sensors/Sensor/Sensor.hpp" -template class PWMSensor { -protected: - uint8_t id; - Type* frequency; - Type* duty_cycle; - -public: - PWMSensor() = default; - PWMSensor(Pin& pin, Type& frequency, Type& duty_cycle); - PWMSensor(Pin& pin, Type* frequency, Type* duty_cycle); - void read(); - uint8_t get_id(); -}; - -template -PWMSensor::PWMSensor(Pin& pin, Type& frequency, Type& duty_cycle) - : frequency(&frequency), duty_cycle(&duty_cycle) { - id = InputCapture::inscribe(pin); - Sensor::inputcapture_id_list.push_back(id); -} - -template -PWMSensor::PWMSensor(Pin& pin, Type* frequency, Type* duty_cycle) - : frequency(frequency), duty_cycle(duty_cycle) { - id = InputCapture::inscribe(pin); - Sensor::inputcapture_id_list.push_back(id); -} - -template void PWMSensor::read() { - *duty_cycle = InputCapture::read_duty_cycle(id); - *frequency = InputCapture::read_frequency(id); -} -template uint8_t PWMSensor::get_id() { return id; } diff --git a/Inc/ST-LIB_LOW/Sensors/Sensor/Sensor.hpp.old b/Inc/ST-LIB_LOW/Sensors/Sensor/Sensor.hpp.old deleted file mode 100644 index edf917b7b..000000000 --- a/Inc/ST-LIB_LOW/Sensors/Sensor/Sensor.hpp.old +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Sensor.hpp - * - * Created on: Nov 2, 2022 - * Author: ricardo - */ - -#pragma once -#include -#include - -class Sensor { -public: - static void start(); - static std::vector inputcapture_id_list; -}; diff --git a/Src/HALAL/HALAL.cpp b/Src/HALAL/HALAL.cpp index 1f43c8d58..7e8d1154b 100644 --- a/Src/HALAL/HALAL.cpp +++ b/Src/HALAL/HALAL.cpp @@ -6,100 +6,9 @@ */ #include "HALAL/HALAL.hpp" -#include "stm32h7xx_hal.h" #include "stm32h7xx_hal_eth.h" #ifndef STLIB_ETH ETH_HandleTypeDef heth; void HAL_ETH_IRQHandler(ETH_HandleTypeDef* heth_arg) { (void)heth_arg; } #endif // STLIB_ETH -namespace HALAL { - -static void common_start(UART::Peripheral& printf_peripheral) { -#ifdef HAL_IWDG_MODULE_ENABLED - Watchdog::check_reset_flag(); -#endif - - HAL_Init(); - HALconfig::system_clock(); - HALconfig::peripheral_clock(); - -#ifdef HAL_UART_MODULE_ENABLED - UART::set_up_printf(printf_peripheral); -#endif - -#ifdef HAL_GPIO_MODULE_ENABLED - Pin::start(); -#endif - -#ifdef HAL_DMA_MODULE_ENABLED - // DMA::start(); -#endif - -#ifdef HAL_MDMA_MODULE_ENABLED - MDMA::start(); -#endif - -#ifdef HAL_FMAC_MODULE_ENABLED - MultiplierAccelerator::start(); -#endif - -#ifdef HAL_CORDIC_MODULE_ENABLED - CORDIC_HandleTypeDef hcordic; - hcordic.Instance = CORDIC; - if (HAL_CORDIC_Init(&hcordic) != HAL_OK) { - PANIC("Unable to init CORDIC"); - } -#endif - -#ifdef HAL_I2C_MODULE_ENABLED - I2C::start(); -#endif - -#ifdef HAL_SPI_MODULE_ENABLED - SPI::start(); -#endif - -#ifdef HAL_UART_MODULE_ENABLED - UART::start(); -#endif - -#ifdef HAL_FDCAN_MODULE_ENABLED - FDCAN::start(); -#endif - -#ifdef HAL_TIM_MODULE_ENABLED - // Encoder::start(); - Global_RTC::start_rtc(); - // TimerPeripheral::start(); - // Time::start(); -#endif - -#ifdef NDEBUG -#ifdef HAL_IWDG_MODULE_ENABLED - Watchdog::start(); -#endif -#endif -} - -#ifdef STLIB_ETH - -void start(MAC mac, IPV4 ip, IPV4 subnet_mask, IPV4 gateway, UART::Peripheral& printf_peripheral) { - Ethernet::inscribe(); - - common_start(printf_peripheral); - - Ethernet::start(mac, ip, subnet_mask, gateway); - -#ifdef HAL_TIM_MODULE_ENABLED - SNTP::sntp_update(); -#endif -} - -#else // !STLIB_ETH - -void start(UART::Peripheral& printf_peripheral) { common_start(printf_peripheral); } - -#endif // STLIB_ETH - -} // namespace HALAL diff --git a/Src/HALAL/Models/TimerPeripheral/TimerPeripheral.cpp.old b/Src/HALAL/Models/TimerPeripheral/TimerPeripheral.cpp.old deleted file mode 100644 index 45ad6920e..000000000 --- a/Src/HALAL/Models/TimerPeripheral/TimerPeripheral.cpp.old +++ /dev/null @@ -1,174 +0,0 @@ -/* - * TimerPeripheral.cpp - * - * Created on: 3 dic. 2022 - * Author: aleja - */ - -#include "HALAL/Models/TimerPeripheral/TimerPeripheral.hpp" - -map TimerPeripheral::handle_to_timer = { - {&htim1, TIM1}, - {&htim2, TIM2}, - {&htim3, TIM3}, - {&htim4, TIM4}, - {&htim8, TIM8}, - {&htim12, TIM12}, - {&htim15, TIM15}, - {&htim16, TIM16}, - {&htim17, TIM17}, - {&htim23, TIM23}, - {&htim24, TIM24} -}; - -TimerPeripheral::InitData::InitData( - TIM_TYPE type, - uint32_t prescaler, - uint32_t period, - uint32_t deadtime, - uint32_t polarity, - uint32_t negated_polarity -) - : type(type), prescaler(prescaler), period(period), deadtime(deadtime), polarity(polarity), - negated_polarity(negated_polarity) {} - -TimerPeripheral::TimerPeripheral(TIM_HandleTypeDef* handle, InitData&& init_data, string name) - : handle(handle), init_data(init_data), name(name) {} - -void TimerPeripheral::init() { - TIM_MasterConfigTypeDef sMasterConfig = {0}; - TIM_IC_InitTypeDef sConfigIC = {0}; - TIM_OC_InitTypeDef sConfigOC = {0}; - TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}; - - handle->Instance = handle_to_timer[handle]; - handle->Init.Prescaler = init_data.prescaler; - handle->Init.CounterMode = TIM_COUNTERMODE_UP; - handle->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; - for (PWMData& pwm_data : init_data.pwm_channels) { - if (pwm_data.mode == PHASED) { - handle->Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1; - break; - } else if (pwm_data.mode == CENTER_ALIGNED) { - handle->Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3; - handle->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; - break; - } - } - handle->Init.Period = init_data.period; - handle->Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - handle->Init.RepetitionCounter = 0; - - if (init_data.type == BASE) { - if (HAL_TIM_Base_Init(handle) != HAL_OK) { - ErrorHandler("Unable to init base timer on %d", name.c_str()); - } - } - - if (!init_data.input_capture_channels.empty()) { - // TO READ LOW FREQUENCIES WE NEED TO PRESCALE - // ALSO NEED CHANGE TIM23 TIMER FROM BASE TO ADVANCE - handle->Init.Prescaler = 2000; - if (HAL_TIM_IC_Init(handle) != HAL_OK) { - ErrorHandler("Unable to init input capture on %d", name.c_str()); - } - } - - if (!init_data.pwm_channels.empty()) { - if (HAL_TIM_PWM_Init(handle) != HAL_OK) { - ErrorHandler("Unable to init PWM on %d", name.c_str()); - } - } - - sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; - sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET; - sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; - if (HAL_TIMEx_MasterConfigSynchronization(handle, &sMasterConfig) != HAL_OK) { - ErrorHandler("Unable to configure master synchronization on %d", name.c_str()); - } - - for (pair& channels_rising_falling : init_data.input_capture_channels) { - sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; - sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; - sConfigIC.ICFilter = 0; - sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; - if (HAL_TIM_IC_ConfigChannel(handle, &sConfigIC, channels_rising_falling.first) != HAL_OK) { - ErrorHandler("Unable to configure a IC channel on %d", name.c_str()); - } - - sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; - sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; - if (HAL_TIM_IC_ConfigChannel(handle, &sConfigIC, channels_rising_falling.second) != - HAL_OK) { - ErrorHandler("Unable to configure a IC channel on %d", name.c_str()); - } - } - - for (PWMData& pwm_data : init_data.pwm_channels) { - sConfigOC.OCPolarity = init_data.polarity; - sConfigOC.OCNPolarity = init_data.negated_polarity; - sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; - sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; - sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; - - if (pwm_data.mode == PHASED) { - // ASSYMETRIC_MODE_1 means one output per pair of registers (CCR1 - CCR2) for example - sConfigOC.OCMode = TIM_OCMODE_ASSYMETRIC_PWM1; - if (HAL_TIM_PWM_ConfigChannel(handle, &sConfigOC, pwm_data.channel) != HAL_OK) { - ErrorHandler("Unable to configure a PWM channel on %d", name.c_str()); - } - // if the channel number is even the pair is the previous channel, example, - // if channel is 2 then CCRX is CCR2 and the pair is CCR1 - // note that TIM_CHANNEL_1 is not 1 is actually 0x00000000, therefore the %8 - if (pwm_data.channel % 8 == 1) { - if (HAL_TIM_PWM_ConfigChannel(handle, &sConfigOC, pwm_data.channel - 4) != HAL_OK) { - ErrorHandler("Unable to configure a PWM channel on %d", name.c_str()); - } - } else { - if (HAL_TIM_PWM_ConfigChannel(handle, &sConfigOC, pwm_data.channel + 4) != HAL_OK) { - ErrorHandler("Unable to configure a PWM channel on %d", name.c_str()); - } - } - } else { - sConfigOC.OCMode = TIM_OCMODE_PWM1; - if (HAL_TIM_PWM_ConfigChannel(handle, &sConfigOC, pwm_data.channel) != HAL_OK) { - ErrorHandler("Unable to configure a PWM channel on %d", name.c_str()); - } - } - } - - sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; - sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; - sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; - sBreakDeadTimeConfig.DeadTime = init_data.deadtime; - sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; - sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; - sBreakDeadTimeConfig.BreakFilter = 0; - sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE; - sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH; - sBreakDeadTimeConfig.Break2Filter = 0; - sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; - if (HAL_TIMEx_ConfigBreakDeadTime(handle, &sBreakDeadTimeConfig) != HAL_OK) { - ErrorHandler("Unable to configure break dead time on %d", name.c_str()); - } -} - -void TimerPeripheral::start() { - for (TimerPeripheral& timer : timers) { - if (timer.is_registered()) { - timer.init(); - } - } -} - -bool TimerPeripheral::is_registered() { - return init_data.pwm_channels.size() + init_data.input_capture_channels.size(); -} - -uint32_t TimerPeripheral::get_prescaler() { return handle->Instance->PSC; } - -uint32_t TimerPeripheral::get_period() { return handle->Instance->ARR; } - -bool TimerPeripheral::is_occupied() { - return init_data.pwm_channels.size() && init_data.input_capture_channels.size(); -} diff --git a/Src/HALAL/Services/InputCapture/InputCapture.cpp.old b/Src/HALAL/Services/InputCapture/InputCapture.cpp.old deleted file mode 100644 index 293119de9..000000000 --- a/Src/HALAL/Services/InputCapture/InputCapture.cpp.old +++ /dev/null @@ -1,147 +0,0 @@ -/* - * InputCapture.cpp - * - * Created on: 30 oct. 2022 - * Author: alejandro - */ -#include "HALAL/Services/InputCapture/InputCapture.hpp" -#include "ErrorHandler/ErrorHandler.hpp" - -uint8_t InputCapture::id_counter = 0; -map InputCapture::active_instances = {}; -static map channel_dict = { - {HAL_TIM_ACTIVE_CHANNEL_1, TIM_CHANNEL_1}, - {HAL_TIM_ACTIVE_CHANNEL_2, TIM_CHANNEL_2}, - {HAL_TIM_ACTIVE_CHANNEL_3, TIM_CHANNEL_3}, - {HAL_TIM_ACTIVE_CHANNEL_4, TIM_CHANNEL_4}, - {HAL_TIM_ACTIVE_CHANNEL_5, TIM_CHANNEL_5}, - {HAL_TIM_ACTIVE_CHANNEL_6, TIM_CHANNEL_6} -}; - -InputCapture::Instance::Instance( - Pin& pin, - TimerPeripheral* peripheral, - uint32_t channel_rising, - uint32_t channel_falling -) - : pin(pin), peripheral(peripheral), channel_rising(channel_rising), - channel_falling(channel_falling) { - frequency = 0; - duty_cycle = 0; -} - -uint8_t InputCapture::inscribe(Pin& pin) { - if (not available_instances.contains(pin) || pin.mode != OperationMode::NOT_USED) { - ErrorHandler( - " The pin %s is already used or isn t available for InputCapture usage", - pin.to_string().c_str() - ); - return 0; - } - - Pin::inscribe(pin, TIMER_ALTERNATE_FUNCTION); - - Instance data = available_instances[pin]; - id_counter++; - active_instances[id_counter] = data; - active_instances[id_counter].id = id_counter; - - vector>& channels = - active_instances[id_counter].peripheral->init_data.input_capture_channels; - uint32_t channel_rising = active_instances[id_counter].channel_rising; - uint32_t channel_falling = active_instances[id_counter].channel_falling; - channels.push_back({channel_rising, channel_falling}); - return id_counter; -} - -void InputCapture::turn_on(uint8_t id) { - if (not active_instances.contains(id)) { - ErrorHandler("ID %d is not registered as an active_instance", id); - return; - } - Instance instance = active_instances[id]; - - if (HAL_TIM_IC_Start_IT(instance.peripheral->handle, instance.channel_rising) != HAL_OK) { - ErrorHandler( - "Unable to start the %s Input Capture measurement in interrupt mode", - instance.peripheral->name.c_str() - ); - } - - if (HAL_TIM_IC_Start(instance.peripheral->handle, instance.channel_falling) != HAL_OK) { - ErrorHandler( - "Unable to start the %s Input Capture measurement", - instance.peripheral->name.c_str() - ); - } -} - -void InputCapture::turn_off(uint8_t id) { - if (not active_instances.contains(id)) { - ErrorHandler("ID %d is not registered as an active_instance", id); - return; - } - Instance instance = active_instances[id]; - if (HAL_TIM_IC_Stop_IT(instance.peripheral->handle, instance.channel_rising) != HAL_OK) { - ErrorHandler( - "Unable to stop the %s Input Capture measurement in interrupt mode", - instance.peripheral->name.c_str() - ); - } - - if (HAL_TIM_IC_Stop(instance.peripheral->handle, instance.channel_falling) != HAL_OK) { - ErrorHandler( - "Unable to stop the %s Input Capture measurement", - instance.peripheral->name.c_str() - ); - } -} - -uint32_t InputCapture::read_frequency(uint8_t id) { - if (not active_instances.contains(id)) { - ErrorHandler("ID %d is not registered as an active_instance", id); - return 0; - } - Instance instance = active_instances[id]; - return instance.frequency; -} - -uint8_t InputCapture::read_duty_cycle(uint8_t id) { - if (not active_instances.contains(id)) { - ErrorHandler("ID %d is not registered as an active_instance", id); - return 0; - } - Instance instance = active_instances[id]; - return instance.duty_cycle; -} - -InputCapture::Instance InputCapture::find_instance_by_channel(uint32_t channel) { - for (auto id_instance : active_instances) { - uint32_t& ch_rising = id_instance.second.channel_rising; - uint32_t& ch_falling = id_instance.second.channel_falling; - - if (ch_rising == channel || ch_falling == channel) { - return id_instance.second; - } - } - - ErrorHandler("Channel %d is not a registered channel", channel); - return Instance(); -} - -void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { - htim->Instance->CNT = 0; - uint32_t& active_channel = channel_dict[htim->Channel]; - InputCapture::Instance instance = InputCapture::find_instance_by_channel(active_channel); - - uint32_t rising_value = HAL_TIM_ReadCapturedValue(htim, instance.channel_rising); - if (rising_value != 0) { - float ref_clock = - HAL_RCC_GetPCLK1Freq() * 2 / (instance.peripheral->handle->Init.Prescaler + 1); - float falling_value = HAL_TIM_ReadCapturedValue(htim, instance.channel_falling); - - InputCapture::active_instances[instance.id].frequency = round(ref_clock / rising_value); - InputCapture::active_instances[instance.id].duty_cycle = - round((falling_value * 100) / rising_value); - } -} diff --git a/Src/HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.cpp.old b/Src/HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.cpp.old deleted file mode 100644 index 4da250d02..000000000 --- a/Src/HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.cpp.old +++ /dev/null @@ -1,108 +0,0 @@ -/* - * DualPhasedPWM.cpp - * - * Created on: 9 mar. 2023 - * Author: aleja - */ - -#include "HALAL/Services/PWM/DualPhasedPWM/DualPhasedPWM.hpp" - -DualPhasedPWM::DualPhasedPWM(Pin& pin, Pin& pin_negated) { - if (not TimerPeripheral::available_dual_pwms.contains({pin, pin_negated})) { - ErrorHandler( - "Pins %s and %s are not registered as an available Dual PHASED PWM", - pin.to_string(), - pin_negated.to_string() - ); - } - - TimerPeripheral& timer = TimerPeripheral::available_dual_pwms.at({pin, pin_negated}).first; - TimerPeripheral::PWMData& pwm_data = - TimerPeripheral::available_dual_pwms.at({pin, pin_negated}).second; - - peripheral = &timer; - channel = pwm_data.channel; - - if (pwm_data.mode != TimerPeripheral::PWM_MODE::PHASED) { - ErrorHandler( - "Pins %s and %s are not registered as a DUAL PHASED PWM", - pin.to_string(), - pin_negated.to_string() - ); - } - - Pin::inscribe(pin, TIMER_ALTERNATE_FUNCTION); - Pin::inscribe(pin_negated, TIMER_ALTERNATE_FUNCTION); - timer.init_data.pwm_channels.push_back(pwm_data); - - duty_cycle = 0; - raw_phase = 0; -} - -void DualPhasedPWM::set_duty_cycle(float duty_cycle) { - this->duty_cycle = duty_cycle; - if (raw_phase > 100.0) { - duty_cycle = 100.0 - duty_cycle; - raw_phase = raw_phase - 100.0; - __STLIB_TIM_SET_MODE(peripheral->handle, channel, STLIB_TIMER_CCMR_PWM_MODE_1) - } else { - __STLIB_TIM_SET_MODE(peripheral->handle, channel, STLIB_TIMER_CCMR_PWM_MODE_2) - } - uint32_t arr = peripheral->handle->Instance->ARR; - float start_high = arr * (50.0 - duty_cycle) / 50.0; - float end_high = arr * (100.0 - duty_cycle) / 50.0 + 1; - if (start_high < 0) { - start_high = 0; - } - if (end_high > arr) { - end_high = arr; - } - float max_range = duty_cycle > 50.0 ? 100 - duty_cycle : duty_cycle; - start_high = start_high + arr * max_range * raw_phase / 5000.0; - end_high = end_high - arr * max_range * raw_phase / 5000.0; - - peripheral->handle->Instance->CR2 &= ~TIM_CR2_CCPC; - peripheral->handle->Instance->CR2 &= ~TIM_CR2_CCUS; - - __HAL_TIM_SET_COMPARE(peripheral->handle, channel, start_high); - - if (channel % 8 == 0) { - __HAL_TIM_SET_COMPARE(peripheral->handle, channel + 4, end_high); - peripheral->handle->Instance->EGR |= TIM_EGR_UG; - peripheral->handle->Instance->CR2 |= TIM_CR2_CCPC; - peripheral->handle->Instance->CR2 |= TIM_CR2_CCUS; - TIM_CCxChannelCmd(peripheral->handle->Instance, channel, TIM_CCx_ENABLE); - TIM_CCxChannelCmd(peripheral->handle->Instance, channel + 4, TIM_CCx_ENABLE); - __HAL_TIM_MOE_ENABLE(peripheral->handle); - } else { - __HAL_TIM_SET_COMPARE(peripheral->handle, channel - 4, end_high); - peripheral->handle->Instance->EGR |= TIM_EGR_UG; - peripheral->handle->Instance->CR2 |= TIM_CR2_CCPC; - peripheral->handle->Instance->CR2 |= TIM_CR2_CCUS; - TIM_CCxChannelCmd(peripheral->handle->Instance, channel, TIM_CCx_ENABLE); - TIM_CCxChannelCmd(peripheral->handle->Instance, channel - 4, TIM_CCx_ENABLE); - __HAL_TIM_MOE_ENABLE(peripheral->handle); - } -} - -void DualPhasedPWM::set_frequency(uint32_t freq_in_hz) { - this->frequency = freq_in_hz; - TIM_TypeDef& timer = *peripheral->handle->Instance; - timer.ARR = (HAL_RCC_GetPCLK1Freq() * 2 / (timer.PSC + 1)) / 2 / frequency; - set_duty_cycle(duty_cycle); -} - -void DualPhasedPWM::set_phase(float phase_in_deg) { - if (duty_cycle == 50.0) { - this->raw_phase = phase_in_deg * (200 / 360); - } else { - // TODO - } - set_duty_cycle(duty_cycle); -} -void DualPhasedPWM::set_raw_phase(float raw_phase) { - this->raw_phase = raw_phase; - set_duty_cycle(duty_cycle); -} - -float DualPhasedPWM::get_phase() const { return raw_phase * 360 / 200; } diff --git a/Src/HALAL/Services/PWM/PhasedPWM/PhasedPWM.cpp.old b/Src/HALAL/Services/PWM/PhasedPWM/PhasedPWM.cpp.old deleted file mode 100644 index 62cbb914d..000000000 --- a/Src/HALAL/Services/PWM/PhasedPWM/PhasedPWM.cpp.old +++ /dev/null @@ -1,126 +0,0 @@ -/* - * PhasedPWM.cpp - * - * Created on: Feb 27, 2023 - * Author: aleja - */ - -#include "HALAL/Services/PWM/PhasedPWM/PhasedPWM.hpp" - -/** - * The function initializes a PhasedPWM object with a given pin and sets its duty cycle and phase to - * 0. - * - * @param pin The pin to which the PhasedPWM object is being attached. - */ -PhasedPWM::PhasedPWM(Pin& pin) { - if (not TimerPeripheral::available_pwm.contains(pin)) { - ErrorHandler("Pin %s is not registered as an available PWM", pin.to_string()); - return; - } - - TimerPeripheral& timer = TimerPeripheral::available_pwm.at(pin).first; - TimerPeripheral::PWMData& pwm_data = TimerPeripheral::available_pwm.at(pin).second; - - if (pwm_data.mode != TimerPeripheral::PWM_MODE::PHASED) { - ErrorHandler("Pin %s is not registered as a PHASED PWM", pin.to_string()); - } - - peripheral = &timer; - channel = pwm_data.channel; - - Pin::inscribe(pin, TIMER_ALTERNATE_FUNCTION); - timer.init_data.pwm_channels.push_back(pwm_data); - - duty_cycle = 0; - raw_phase = 0; - is_initialized = true; -} - -/** - * This function sets the duty cycle of a PWM signal and calculates the raw duty and phase values - * based on the input duty cycle and phase. - * - * @param duty_cycle The duty cycle is a value between 0 and 100 that represents the percentage of - * time that the PWM signal is high compared to the total period of the signal. - */ -void PhasedPWM::set_duty_cycle(float duty_cycle) { - this->duty_cycle = duty_cycle; - if (raw_phase > 100.0) { - duty_cycle = 100.0 - duty_cycle; - raw_phase = raw_phase - 100.0; - __STLIB_TIM_SET_MODE(peripheral->handle, channel, STLIB_TIMER_CCMR_PWM_MODE_1) - } else { - __STLIB_TIM_SET_MODE(peripheral->handle, channel, STLIB_TIMER_CCMR_PWM_MODE_2) - } - uint32_t arr = peripheral->handle->Instance->ARR; - float start_high = arr * (50.0 - duty_cycle) / 50.0; - float end_high = arr * (100.0 - duty_cycle) / 50.0 + 1; - if (start_high < 0) { - start_high = 0; - } - if (end_high > arr) { - end_high = arr; - } - float max_range = duty_cycle > 50.0 ? 100 - duty_cycle : duty_cycle; - start_high = start_high + arr * max_range * raw_phase / 5000.0; - end_high = end_high - arr * max_range * raw_phase / 5000.0; - - peripheral->handle->Instance->CR2 &= ~TIM_CR2_CCPC; - peripheral->handle->Instance->CR2 &= ~TIM_CR2_CCUS; - - __HAL_TIM_SET_COMPARE(peripheral->handle, channel, start_high); - - if (channel % 8 == 0) { - __HAL_TIM_SET_COMPARE(peripheral->handle, channel + 4, end_high); - peripheral->handle->Instance->EGR |= TIM_EGR_UG; - peripheral->handle->Instance->CR2 |= TIM_CR2_CCPC; - peripheral->handle->Instance->CR2 |= TIM_CR2_CCUS; - TIM_CCxChannelCmd(peripheral->handle->Instance, channel, TIM_CCx_ENABLE); - TIM_CCxChannelCmd(peripheral->handle->Instance, channel + 4, TIM_CCx_ENABLE); - __HAL_TIM_MOE_ENABLE(peripheral->handle); - } else { - __HAL_TIM_SET_COMPARE(peripheral->handle, channel - 4, end_high); - peripheral->handle->Instance->EGR |= TIM_EGR_UG; - peripheral->handle->Instance->CR2 |= TIM_CR2_CCPC; - peripheral->handle->Instance->CR2 |= TIM_CR2_CCUS; - TIM_CCxChannelCmd(peripheral->handle->Instance, channel, TIM_CCx_ENABLE); - TIM_CCxChannelCmd(peripheral->handle->Instance, channel - 4, TIM_CCx_ENABLE); - __HAL_TIM_MOE_ENABLE(peripheral->handle); - } -} - -/** - * This function sets the frequency of a PWM signal using a timer in a microcontroller. - * - * @param frequency The desired frequency of the PWM signal in Hertz (Hz). - */ -void PhasedPWM::set_frequency(uint32_t frequency) { - TIM_TypeDef& timer = *peripheral->handle->Instance; - timer.ARR = (HAL_RCC_GetPCLK1Freq() * 2 / (timer.PSC + 1)) / 2 / frequency; - this->frequency = frequency; - set_duty_cycle(duty_cycle); -} - -/** - * This function sets the phase of a PhasedPWM object and updates the duty cycle accordingly. - * - * @param phase The "phase" parameter is a floating-point value that represents the phase shift of a - * PWM signal. In other words, it determines the timing offset of the PWM waveform relative to its - * center. Only works with duty cycle = 50 for now. - */ -void PhasedPWM::set_phase(float phase_in_deg) { - if (duty_cycle == 50.0) { - this->raw_phase = phase_in_deg * (200.0 / 360.0); - } else { - // TODO - } - set_duty_cycle(duty_cycle); -} - -void PhasedPWM::set_raw_phase(float raw_phase) { - this->raw_phase = raw_phase; - set_duty_cycle(duty_cycle); -} - -float PhasedPWM::get_phase() const { return raw_phase * 360 / 200; } diff --git a/Src/ST-LIB.cpp b/Src/ST-LIB.cpp deleted file mode 100644 index 2f93ddf99..000000000 --- a/Src/ST-LIB.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "ST-LIB.hpp" - -#ifdef STLIB_ETH - -// Con Ethernet: interfaz con MAC/IP + overload con strings -void STLIB::start( - MAC mac, - IPV4 ip, - IPV4 subnet_mask, - IPV4 gateway, - UART::Peripheral& printf_peripheral -) { - HALAL::start(mac, ip, subnet_mask, gateway, printf_peripheral); -} - -void STLIB::start( - const std::string& mac, - const std::string& ip, - const std::string& subnet_mask, - const std::string& gateway, - UART::Peripheral& printf_peripheral -) { - STLIB::start(MAC(mac), IPV4(ip), IPV4(subnet_mask), IPV4(gateway), printf_peripheral); -} - -#else // !STLIB_ETH - -void STLIB::start(UART::Peripheral& printf_peripheral) { HALAL::start(printf_peripheral); } - -#endif // STLIB_ETH - -void STLIB::update() { -#ifdef NDEBUG -#ifdef HAL_IWDG_MODULE_ENABLED - Watchdog::refresh(); -#endif -#endif - -#if !defined STLIB_ETH -#else - Ethernet::update(); - Server::update_servers(); -#endif - Diagnostics::Hub::flush(); - MDMA::update(); -} diff --git a/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp b/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp deleted file mode 100644 index c73468cbc..000000000 --- a/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp +++ /dev/null @@ -1,10 +0,0 @@ -/* - * ST-LIB_LOW.cpp - * - * Created on: 19 jun. 2023 - * Author: ricardo - */ - -#include "ST-LIB_HIGH.hpp" - -void STLIB_HIGH::start() { ProtectionEngine::initialize(); } diff --git a/Src/ST-LIB_LOW/Clocks/Stopwatch.cpp.old b/Src/ST-LIB_LOW/Clocks/Stopwatch.cpp.old deleted file mode 100644 index 44935b563..000000000 --- a/Src/ST-LIB_LOW/Clocks/Stopwatch.cpp.old +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Time.hpp - * - * Created on: 11 nov. 2022 - * Author: Dani - */ - -#include "Clocks/Stopwatch.hpp" - -#include "HALAL/Services/Time/Time.hpp" - -void Stopwatch::start(const string id) { start_times[id] = Time::get_global_tick(); } - -uint64_t Stopwatch::stop(const string id) { - if (not start_times.contains(id)) { - ErrorHandler("No encoder registered with id %u", id); - return 0; - } - - uint64_t result = Time::get_global_tick() - start_times[id]; - start(id); - return result; -} diff --git a/Src/ST-LIB_LOW/HalfBridge/HalfBridge.cpp.old b/Src/ST-LIB_LOW/HalfBridge/HalfBridge.cpp.old deleted file mode 100644 index f1934027a..000000000 --- a/Src/ST-LIB_LOW/HalfBridge/HalfBridge.cpp.old +++ /dev/null @@ -1,65 +0,0 @@ -/* - * HalfBridge.cpp - * - * Created on: Dec 1, 2022 - * Author: aleja - */ - -#include - -#include "HALAL/Services/DigitalOutputService/DigitalOutputService.hpp" - -HalfBridge::HalfBridge( - Pin& positive_pwm_pin, - Pin& positive_pwm_negated_pin, - Pin& negative_pwm_pin, - Pin& negative_pwm_negated_pin, - Pin& enable_pin -) - : is_dual(true), positive_pwm{positive_pwm_pin, positive_pwm_negated_pin}, - negative_pwm{negative_pwm_pin, negative_pwm_negated_pin} { - HalfBridge::enable = DigitalOutputService::inscribe(enable_pin); -} - -void HalfBridge::turn_on() { - positive_pwm.turn_on(); - negative_pwm.turn_on(); - DigitalOutputService::turn_on(enable); // enable at the end to avoid noise - DigitalOutputService::set_pin_state(enable, PinState::OFF); -} - -void HalfBridge::turn_off() { - DigitalOutputService::turn_off(enable); // enable at the start to avoid noise - positive_pwm.turn_off(); - negative_pwm.turn_off(); -} - -void HalfBridge::set_duty_cycle(float duty_cycle) { - if (duty_cycle != 50) { - ErrorHandler("Cannot modify duty cycle in HalfBridge"); - return; - } - positive_pwm.set_duty_cycle(duty_cycle); - negative_pwm.set_duty_cycle(duty_cycle); -} - -void HalfBridge::set_frequency(int32_t frequency) { - positive_pwm.set_frequency(frequency); - negative_pwm.set_frequency(frequency); -} - -void HalfBridge::set_phase(float phase) { - positive_pwm.set_phase(0); - negative_pwm.set_phase(phase); -} -void HalfBridge::set_negative_pwm_phase(float phase) { - negative_pwm.set_phase(positive_pwm.get_phase() + phase); -} -void HalfBridge::set_positive_pwm_phase(float phase) { - positive_pwm.set_phase(negative_pwm.get_phase() + phase); -} - -float HalfBridge::get_phase() { - return negative_pwm.get_phase(); - ; -} diff --git a/Src/ST-LIB_LOW/ST-LIB_LOW.cpp b/Src/ST-LIB_LOW/ST-LIB_LOW.cpp deleted file mode 100644 index 9b4365f44..000000000 --- a/Src/ST-LIB_LOW/ST-LIB_LOW.cpp +++ /dev/null @@ -1,12 +0,0 @@ -/* - * ST-LIB_LOW.cpp - * - * Created on: 5 ene. 2023 - * Author: aleja - */ - -#include "ST-LIB_LOW.hpp" - -void STLIB_LOW::start() { - // Sensor::start(); -} diff --git a/Src/ST-LIB_LOW/Sensors/Sensor/Sensor.cpp.old b/Src/ST-LIB_LOW/Sensors/Sensor/Sensor.cpp.old deleted file mode 100644 index 251dc3619..000000000 --- a/Src/ST-LIB_LOW/Sensors/Sensor/Sensor.cpp.old +++ /dev/null @@ -1,12 +0,0 @@ -#include "Sensors/Sensor/Sensor.hpp" - -#include "HALAL/Services/InputCapture/InputCapture.hpp" - -std::vector Sensor::inputcapture_id_list{}; - -void Sensor::start() { - - for (uint8_t inputcapture_id : inputcapture_id_list) { - InputCapture::turn_on(inputcapture_id); - } -} From b9445daf0f1ccaf2751672f839edebfb4a422e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 22 Apr 2026 21:35:39 +0200 Subject: [PATCH 16/23] Preserve early faults across bootstrap and replay retained diagnostics --- .../Services/Diagnostics/Diagnostics.hpp | 5 ++ Inc/ST-LIB.hpp | 6 +-- .../Protections/FaultController.hpp | 14 +++-- .../Services/Diagnostics/DiagnosticsHub.cpp | 14 +++++ Tests/TestAccess.hpp | 10 ++++ Tests/diagnostics_test.cpp | 54 +++++++++++++++++++ 6 files changed, 96 insertions(+), 7 deletions(-) diff --git a/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp index 596de6e91..28d5c5dfe 100644 --- a/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp +++ b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp @@ -148,6 +148,7 @@ class Hub { public: template static expected emplace_sink(Args&&... args) { + const bool had_no_sinks = sink_count == 0; if (sink_count >= Config::max_sinks) { return unexpected(RegistrationError::CAPACITY_EXCEEDED); } @@ -162,6 +163,9 @@ class Hub { slot.sink = sink; slot.destroy = [](DiagnosticSink* base) { destroy_at(static_cast(base)); }; sinks[sink_count++] = sink; + if (had_no_sinks) { + replay_history_to_pending(); + } return sink; } } @@ -228,6 +232,7 @@ class Hub { static void push_history(const DiagnosticRecord& record); static void push_pending(const DiagnosticRecord& record); + static void replay_history_to_pending(); static void remove_pending(size_t index); static size_t find_oldest_normal_pending(); static void flush_pending(bool urgent_only); diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index a518d269e..04f7f552a 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -266,13 +266,13 @@ template struct Board { Watchdog::check_reset_flag(); Hard_fault_check(); #endif + Diagnostics::Runtime::install_default_sinks(); + FaultController::template install_runtime(); + HAL_Init(); HALconfig::system_clock(); HALconfig::peripheral_clock(); - Diagnostics::Runtime::install_default_sinks(); - FaultController::template install_runtime(); - #ifdef HAL_RTC_MODULE_ENABLED (void)Global_RTC::ensure_started(); #endif diff --git a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp index 91203e180..f82ef243f 100644 --- a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp +++ b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp @@ -62,6 +62,10 @@ class FaultController { static constexpr size_t max_runtime_storage = 2048; template static void install_runtime() { + const bool preserve_preinstalled_fault = + runtime_storage.machine == nullptr && !runtime_started && faulted && has_latched_cause; + const FaultCause preserved_cause = latched_cause; + static_assert( requires { { Policy::has_operational_machine } -> std::convertible_to; @@ -71,10 +75,12 @@ class FaultController { ); runtime_started = false; - faulted = false; - has_latched_cause = false; - latched_cause = {}; - reconstruct_runtime_machine(RuntimeState::OPERATIONAL); + faulted = preserve_preinstalled_fault; + has_latched_cause = preserve_preinstalled_fault; + latched_cause = preserve_preinstalled_fault ? preserved_cause : FaultCause{}; + reconstruct_runtime_machine( + preserve_preinstalled_fault ? RuntimeState::FAULT : RuntimeState::OPERATIONAL + ); } static void start(); diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp index 3b0fdebf7..b32cdae27 100644 --- a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp +++ b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp @@ -78,6 +78,20 @@ void Hub::push_pending(const DiagnosticRecord& record) { pending_count++; } +void Hub::replay_history_to_pending() { + if (sink_count == 0 || history_count == 0 || pending_count != 0) { + return; + } + + const size_t oldest_index = + history_count == Config::history_capacity ? history_next_index : 0; + + for (size_t replay_index = 0; replay_index < history_count; ++replay_index) { + const size_t history_index = (oldest_index + replay_index) % Config::history_capacity; + push_pending(history[history_index]); + } +} + void Hub::remove_pending(size_t index) { if (index >= pending_count) { return; diff --git a/Tests/TestAccess.hpp b/Tests/TestAccess.hpp index 8abf32400..1e4fa4ae9 100644 --- a/Tests/TestAccess.hpp +++ b/Tests/TestAccess.hpp @@ -41,6 +41,16 @@ struct ProtectionEngine { }; struct FaultController { + static void clear() { + ::FaultController::reset_runtime_storage(); + ::FaultController::global_machine = nullptr; + ::FaultController::on_fault_enter = nullptr; + ::FaultController::latched_cause = {}; + ::FaultController::has_latched_cause = false; + ::FaultController::faulted = false; + ::FaultController::runtime_started = false; + } + static void request_fault(const ::FaultCause& cause) { ::FaultController::request_fault(cause); } diff --git a/Tests/diagnostics_test.cpp b/Tests/diagnostics_test.cpp index e7c0ef176..9195f0ddc 100644 --- a/Tests/diagnostics_test.cpp +++ b/Tests/diagnostics_test.cpp @@ -102,6 +102,7 @@ class DiagnosticsHubTest : public ::testing::Test { void SetUp() override { TestAccess::DiagnosticsHub::clear(); TestAccess::ProtectionEngine::clear(); + TestAccess::FaultController::clear(); reset_operational_machine(); TestPanicReporter::reset(); fault_enter_calls = 0; @@ -125,6 +126,25 @@ TEST_F(DiagnosticsHubTest, KeepsLocalHistoryWhenNoSinksAreRegistered) { EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), 0u); } +TEST_F(DiagnosticsHubTest, HistoryIsReplayedWhenFirstSinkIsInstalled) { + Diagnostics::DiagnosticRecord record{}; + record.severity = Diagnostics::Severity::WARNING; + record.category = Diagnostics::Category::RUNTIME_WARNING; + snprintf(record.origin, sizeof(record.origin), "test"); + snprintf(record.payload.runtime.message, sizeof(record.payload.runtime.message), "replay me"); + Diagnostics::Hub::publish(record); + + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + + EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), 1u); + + Diagnostics::Hub::flush(); + EXPECT_EQ(sink->publish_calls, 1u); + EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), 0u); +} + TEST_F(DiagnosticsHubTest, RetriesOnlyTheSinkThatFailed) { auto stable_sink_result = Diagnostics::Hub::emplace_sink(); auto flaky_sink_result = Diagnostics::Hub::emplace_sink(1); @@ -241,6 +261,40 @@ TEST_F(DiagnosticsHubTest, FaultControllerStopsDelegatingAfterFault) { EXPECT_EQ(fault_enter_calls, 1u); } +TEST(DiagnosticsBootstrapTest, PanicBeforeRuntimeInstallationSurvivesBootstrapAndIsDelivered) { + TestAccess::DiagnosticsHub::clear(); + TestAccess::ProtectionEngine::clear(); + TestAccess::FaultController::clear(); + reset_operational_machine(); + TestPanicReporter::reset(); + TestPanicReporter::set_fail_on_error(false); + fault_enter_calls = 0; + Scheduler::global_tick_us_ = 0; + Scheduler_global_timer = nullptr; + + PANIC("panic before install"); + + ASSERT_TRUE(FaultController::is_faulted()); + ASSERT_NE(FaultController::latched_fault_cause(), nullptr); + EXPECT_EQ(TestAccess::DiagnosticsHub::history_size(), 1u); + EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), 0u); + + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + + EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), 1u); + + FaultController::install_runtime(); + ASSERT_TRUE(FaultController::is_faulted()); + ASSERT_NE(FaultController::latched_fault_cause(), nullptr); + + FaultController::start(); + EXPECT_EQ(fault_enter_calls, 1u); + EXPECT_EQ(sink->publish_calls, 1u); + EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), 0u); +} + TEST_F(DiagnosticsHubTest, ProtectionEngineEvaluatesRulesAndPublishesSnapshots) { float monitored_value = 2.0f; SampleSource source(monitored_value); From dba8ceab5e9d9eb29d0aac49d441d00da2563408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 22 Apr 2026 21:35:47 +0200 Subject: [PATCH 17/23] Document the updated protections and fault contract --- docs/protections-and-diagnostics.md | 97 +++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 4 deletions(-) diff --git a/docs/protections-and-diagnostics.md b/docs/protections-and-diagnostics.md index bce5b8976..ecfc3c908 100644 --- a/docs/protections-and-diagnostics.md +++ b/docs/protections-and-diagnostics.md @@ -42,6 +42,19 @@ flowchart TD D --> G["Diagnostics::Hub::flush()"] ``` +The application integration contract is: + +```cpp +Board +``` + +Where: + +- `FaultPolicyT` is mandatory and is always the first template argument +- `dev0, dev1, ...` are the board declarations to inscribe into the domains +- the framework always owns the top-level runtime machine +- the application may optionally provide a nested operational machine and/or a `FAULT` entry callback + ### 1.2 Registering Protections Protections are created through `ProtectionEngine::create_protection(...)`. @@ -66,6 +79,33 @@ Available rule factories: Both `create_protection(...)` and `add_rule(...)` return `std::expected`, so configuration errors must be handled explicitly. +Current rule signatures are: + +```cpp +Rules::below(fault_threshold) +Rules::below(fault_threshold, warning_threshold) + +Rules::above(fault_threshold) +Rules::above(fault_threshold, warning_threshold) + +Rules::range(low_fault, high_fault) +Rules::range(low_fault, high_fault, low_warning, high_warning) + +Rules::equals(value) +Rules::not_equals(value) + +Rules::time_accumulation(fault_threshold, window_seconds) +Rules::time_accumulation(fault_threshold, warning_threshold, window_seconds) +``` + +`Rules::time_accumulation(...)` has these semantics: + +- it is intended for floating-point samples +- it evaluates `abs(sample)` +- it measures continuous active time, not an integral over samples +- it resets the accumulated active time when the triggering condition clears +- it uses `Scheduler::get_global_tick()`, so it does not depend on the `while (1)` iteration rate + ### 1.3 When to Register Protections Register protections before `Board::init()`. @@ -78,6 +118,10 @@ The intended lifecycle is: After `Board::init()`, the protection registry is locked. +If registration code reports a fatal condition before `Board::init()`, that fatal request is still +preserved across the first fault-runtime installation and its diagnostic record remains eligible for +later delivery once sinks are installed. + ### 1.4 Typical Protection Example ```cpp @@ -135,6 +179,7 @@ Typical choices are: - `Board` when no extra fault callback is needed - `Board, ...>` when only `FAULT` entry actions are needed +- `Board, ...>` when both a nested operational machine and `FAULT` entry actions are needed If the application does use a functional state machine, it can be nested inside `OPERATIONAL` through a `FaultPolicy`. @@ -176,6 +221,21 @@ Important rules: the child machine directly - `Board` takes the fault policy type as its first template argument +`on_fault_enter` semantics: + +- it is an optional callback owned by the global fault runtime +- it runs when the global runtime enters `FAULT` +- it is the right place to perform application fault-entry actions such as disabling power stages, + opening contactors, or setting status LEDs +- it does not replace the fault transition itself; it is an enter action attached to the global + `FAULT` state + +If the application needs neither a nested machine nor a `FAULT` entry action, use: + +```cpp +using MainBoard = Board; +``` + ### 1.6 Runtime Diagnostics API The runtime diagnostic façade is: @@ -203,10 +263,19 @@ Internally, protections and fatal runtime reporters converge on: FaultController::request_fault(cause); ``` -This primitive is not intended to be the normal user-facing API. +This primitive is not part of the normal user-facing API. +In the current implementation it is an internal `FaultController` entry point, not a public +application hook. + User code should prefer `FAULT(...)` or `PANIC(...)` so the library captures consistent source metadata and preserves the public runtime contract. +In practice: + +- protections use `FaultController::request_fault(...)` internally +- `PANIC(...)` and `FAULT(...)` use that same path internally +- user application code should not call `request_fault(...)` directly + ### 1.8 Transmission Semantics All external reporting goes through `Diagnostics`. @@ -226,6 +295,22 @@ Default sinks are installed during `Board::init()`: If a transport is not compiled in, it is simply not installed. +### 1.9 Migration From the Legacy Model + +If you are migrating from the previous architecture: + +- stop using `ProtectionManager` +- stop using the low/high protection split +- stop using `Boundary` / `BoundaryInterface` as the protection integration model +- stop depending on `FaultRuntime` +- stop treating `STLIB::start()`, `STLIB::update()`, `STLIB_LOW::start()`, or `STLIB_HIGH::start()` + as the real bootstrap path +- move bootstrap to `Board::init()` +- declare `Board` explicitly +- move operational user behavior into `FaultPolicy` when needed +- stop programming transitions to the global `FAULT` +- replace legacy reporting paths with `PANIC(...)`, `FAULT(...)`, `WARNING(...)`, and `INFO(...)` + ## 2. Internal Development ### 2.1 Architectural Overview @@ -317,12 +402,12 @@ Important invariant: The fault path is valid during `Board::init()`. -That is why `Board::init()` installs: +That is why `Board::init()` installs, as early as possible in the bootstrap path: - default diagnostic sinks - the global fault runtime -before subsystem initialization that may trigger `PANIC(...)`. +before clock/peripheral setup and before subsystem initialization that may trigger `PANIC(...)`. If a fatal request arrives before the global runtime has been started: @@ -330,7 +415,11 @@ If a fatal request arrives before the global runtime has been started: - the runtime is rebuilt so that it starts directly in `FAULT` - the urgent fault diagnostic is still published through `Diagnostics` -This avoids losing early boot faults. +If the diagnostic record is produced before any sink exists, it is still retained in local history. +When the first sink is installed, the retained history is replayed into the pending queue so the +record can still be delivered later. + +This avoids losing early boot faults and other pre-transport diagnostics. ### 2.5 FaultCause and Diagnostic Mapping From 4a479ec50c4d4414dc7bb316f96f4c3d30cc2664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 22 Apr 2026 21:57:54 +0200 Subject: [PATCH 18/23] Fix CI regressions and address Copilot review feedback --- Inc/HALAL/Models/TimerDomain/TimerDomain.hpp | 38 ++++++------- Inc/HALAL/Services/Time/Scheduler.hpp | 7 +-- .../Services/Diagnostics/DiagnosticsHub.cpp | 3 +- Src/HALAL/Services/Time/Scheduler.cpp | 10 ++++ .../Protections/ProtectionEngine.cpp | 4 +- Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp | 54 +++++++++---------- Tests/dfsdm_test.cpp | 10 ++-- 7 files changed, 65 insertions(+), 61 deletions(-) diff --git a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp index cc73df75d..de0ef1453 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -806,13 +806,13 @@ struct TimerDomain { sMasterConfig.MasterOutputTrigger = static_cast(e.trgo1); sMasterConfig.MasterOutputTrigger2 = static_cast(e.trgo2); sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; - inst->master = sMasterConfig; - if (HAL_TIMEx_MasterConfigSynchronization(inst->hal_tim, &sMasterConfig) != - HAL_OK) { - PANIC("Unable to configure master synch"); - } - } - } + inst->master = sMasterConfig; + if (HAL_TIMEx_MasterConfigSynchronization(inst->hal_tim, &sMasterConfig) != + HAL_OK) { + PANIC("Unable to configure master synch"); + } + } + } }; static consteval uint8_t get_channel_state_idx(const ST_LIB::TimerChannel ch) { @@ -871,18 +871,18 @@ struct TimerDomain { if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) != RCC_HCLK_DIV1) { result *= 2; } - } else if ((tim == TIM1) || (tim == TIM8) || (tim == TIM15) || (tim == TIM16) || (tim == TIM17) || (tim == TIM23) || (tim == TIM24)) { - result = HAL_RCC_GetPCLK2Freq(); - if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { - result *= 2; - } - } else { - PANIC("Invalid timer ptr"); - } - - return result; - } -}; + } else if ((tim == TIM1) || (tim == TIM8) || (tim == TIM15) || (tim == TIM16) || (tim == TIM17) || (tim == TIM23) || (tim == TIM24)) { + result = HAL_RCC_GetPCLK2Freq(); + if ((RCC->D2CFGR & RCC_D2CFGR_D2PPRE2) != RCC_HCLK_DIV1) { + result *= 2; + } + } else { + PANIC("Invalid timer ptr"); + } + + return result; + } +}; consteval GPIODomain::AlternateFunction TimerDomain::Timer::get_gpio_af(ST_LIB::TimerRequest req, ST_LIB::TimerPin pin) { diff --git a/Inc/HALAL/Services/Time/Scheduler.hpp b/Inc/HALAL/Services/Time/Scheduler.hpp index c37d2e53e..ceb16e198 100644 --- a/Inc/HALAL/Services/Time/Scheduler.hpp +++ b/Inc/HALAL/Services/Time/Scheduler.hpp @@ -41,12 +41,7 @@ struct Scheduler { // temporary, will be removed [[deprecated]] static inline void start() {} static void update(); - static inline uint64_t get_global_tick() { - if (Scheduler_global_timer == nullptr) { - return global_tick_us_; - } - return global_tick_us_ + Scheduler_global_timer->CNT; - } + static uint64_t get_global_tick(); static uint16_t register_task(uint32_t period_us, callback_t func); static bool unregister_task(uint16_t id); diff --git a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp index b32cdae27..9bad45779 100644 --- a/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp +++ b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp @@ -83,8 +83,7 @@ void Hub::replay_history_to_pending() { return; } - const size_t oldest_index = - history_count == Config::history_capacity ? history_next_index : 0; + const size_t oldest_index = history_count == Config::history_capacity ? history_next_index : 0; for (size_t replay_index = 0; replay_index < history_count; ++replay_index) { const size_t history_index = (oldest_index + replay_index) % Config::history_capacity; diff --git a/Src/HALAL/Services/Time/Scheduler.cpp b/Src/HALAL/Services/Time/Scheduler.cpp index 6dd370e85..dd76b155f 100644 --- a/Src/HALAL/Services/Time/Scheduler.cpp +++ b/Src/HALAL/Services/Time/Scheduler.cpp @@ -100,6 +100,16 @@ void Scheduler::update() { } } +uint64_t Scheduler::get_global_tick() { + SchedLock(); + uint64_t tick = global_tick_us_; + if (Scheduler_global_timer != nullptr) { + tick += Scheduler_global_timer->CNT; + } + SchedUnlock(); + return tick; +} + inline uint8_t Scheduler::allocate_slot() { uint32_t idx = __builtin_ffs(Scheduler::free_bitmap_) - 1; if (idx >= Scheduler::kMaxTasks) [[unlikely]] diff --git a/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp b/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp index 8051c6db4..f10050af8 100644 --- a/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp +++ b/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp @@ -76,10 +76,10 @@ void ProtectionEngine::evaluate() { visit( [](auto& protection) { const Protections::ProtectionEvaluation evaluation = protection.evaluate(); - publish_edge_events(protection, evaluation); + ProtectionEngine::publish_edge_events(protection, evaluation); if (evaluation.has_active_fault) { - request_fault_if_due(protection, evaluation); + ProtectionEngine::request_fault_if_due(protection, evaluation); } }, *protections[protection_index] diff --git a/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp b/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp index 01a559e13..286b09f7f 100644 --- a/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp +++ b/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp @@ -5,32 +5,16 @@ namespace { -void trigger_runtime_fatal( - bool panic, - const std::source_location& location, - const char* format, - va_list arguments -) { +struct RuntimeFatalMessage { char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; - const int32_t written = vsnprintf(buffer, sizeof(buffer), format, arguments); - - if (panic) { - FaultController::request_fault(FaultCause::panic( - buffer, - written < 0 || static_cast(written) >= sizeof(buffer), - static_cast(location.line()), - location.function_name(), - location.file_name() - )); - } else { - FaultController::request_fault(FaultCause::runtime_fault( - buffer, - written < 0 || static_cast(written) >= sizeof(buffer), - static_cast(location.line()), - location.function_name(), - location.file_name() - )); - } + bool truncated{false}; +}; + +RuntimeFatalMessage format_runtime_fatal_message(const char* format, va_list arguments) { + RuntimeFatalMessage message{}; + const int32_t written = vsnprintf(message.buffer, sizeof(message.buffer), format, arguments); + message.truncated = written < 0 || static_cast(written) >= sizeof(message.buffer); + return message; } } // namespace @@ -38,15 +22,31 @@ void trigger_runtime_fatal( void FaultReporter::Trigger(const std::source_location& location, const char* format, ...) { va_list arguments; va_start(arguments, format); - trigger_runtime_fatal(false, location, format, arguments); + const RuntimeFatalMessage message = format_runtime_fatal_message(format, arguments); va_end(arguments); + + FaultController::request_fault(FaultCause::runtime_fault( + message.buffer, + message.truncated, + static_cast(location.line()), + location.function_name(), + location.file_name() + )); } void PanicReporter::Trigger(const std::source_location& location, const char* format, ...) { va_list arguments; va_start(arguments, format); - trigger_runtime_fatal(true, location, format, arguments); + const RuntimeFatalMessage message = format_runtime_fatal_message(format, arguments); va_end(arguments); + + FaultController::request_fault(FaultCause::panic( + message.buffer, + message.truncated, + static_cast(location.line()), + location.function_name(), + location.file_name() + )); } void PanicReporter::Flush() { Diagnostics::Hub::flush(); } diff --git a/Tests/dfsdm_test.cpp b/Tests/dfsdm_test.cpp index ad3a31576..163833178 100644 --- a/Tests/dfsdm_test.cpp +++ b/Tests/dfsdm_test.cpp @@ -8,11 +8,11 @@ #include "MockedDrivers/NVIC.hpp" #include "MockedDrivers/mocked_hal_dma.hpp" -namespace ST_LIB::TestErrorHandler { +namespace ST_LIB::TestPanicReporter { void reset(); void set_fail_on_error(bool enabled); extern int call_count; -} // namespace ST_LIB::TestErrorHandler +} // namespace ST_LIB::TestPanicReporter namespace { @@ -111,11 +111,11 @@ static_assert( class DFSDMTest : public ::testing::Test { protected: void SetUp() override { - ST_LIB::TestErrorHandler::reset(); - ST_LIB::TestErrorHandler::set_fail_on_error(true); + ST_LIB::TestPanicReporter::reset(); + ST_LIB::TestPanicReporter::set_fail_on_error(true); } - void TearDown() override { ST_LIB::TestErrorHandler::set_fail_on_error(false); } + void TearDown() override { ST_LIB::TestPanicReporter::set_fail_on_error(false); } }; TEST_F(DFSDMTest, ChannelConfigurationIsValidAtCompileTime) { From 5c4e941e7a053125cc072112c150c08c3a428101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Wed, 22 Apr 2026 21:58:10 +0200 Subject: [PATCH 19/23] Make simulator MPU support portable on macOS --- Inc/HALAL/Models/MPU.hpp | 74 ++++++++++++++++++++++-------- Inc/HALAL/Services/DFSDM/DFSDM.hpp | 26 ++++++----- 2 files changed, 70 insertions(+), 30 deletions(-) diff --git a/Inc/HALAL/Models/MPU.hpp b/Inc/HALAL/Models/MPU.hpp index 915a40634..d74dd50b1 100644 --- a/Inc/HALAL/Models/MPU.hpp +++ b/Inc/HALAL/Models/MPU.hpp @@ -44,6 +44,15 @@ // Defines for attributes // Note1: Variables declared with these attributes will likely not be initialized by the startup // Note2: These attributes can only be used for static/global variables +#ifdef SIM_ON +#define D1_NC +#define D2_NC +#define D3_NC +#define D1_C +#define D2_C +#define D3_C +#define RAM_CODE +#else #define D1_NC __attribute__((section(".mpu_ram_d1_nc.user"))) #define D2_NC __attribute__((section(".mpu_ram_d2_nc.user"))) #define D3_NC __attribute__((section(".mpu_ram_d3_nc.user"))) @@ -53,6 +62,7 @@ // Define for RAM code #define RAM_CODE __attribute__((section(".ram_code"))) +#endif // Memory Bank Symbols from Linker extern "C" const char __itcm_base; @@ -78,6 +88,17 @@ extern "C" const char __mpu_d2_nc_end; extern "C" const char __mpu_d3_nc_start; extern "C" const char __mpu_d3_nc_end; +inline constexpr std::array mpu_supported_alignments = {32, 16, 8, 4, 2, 1}; + +consteval bool is_supported_mpu_alignment(std::size_t alignment) { + for (std::size_t candidate : mpu_supported_alignments) { + if (candidate == alignment) { + return true; + } + } + return false; +} + template concept mpu_buffer_request = requires(typename T::domain d) { typename T::buffer_type; @@ -133,7 +154,7 @@ struct MPUDomain { "Requested type has alignment greater than cache line size (32 bytes)." ); static_assert( - std::ranges::find(alignments, alignof(T)) != std::ranges::end(alignments), + is_supported_mpu_alignment(alignof(T)), "Requested type has alignment not supported by MPU buffer system." ); } @@ -143,14 +164,12 @@ struct MPUDomain { * @param entry The Entry with all buffer requirements specified. */ consteval Buffer(Entry entry) : e(entry) { - static_assert( - entry.alignment <= 32, - "Requested alignment greater than cache line size (32 bytes)." - ); - static_assert( - std::ranges::find(alignments, entry.alignment) != std::ranges::end(alignments), - "Requested alignment not supported by MPU buffer system." - ); + if (entry.alignment > 32) { + compile_error("Requested alignment greater than cache line size (32 bytes)."); + } + if (!is_supported_mpu_alignment(entry.alignment)) { + compile_error("Requested alignment not supported by MPU buffer system."); + } // Verify size matches sizeof(T) if (entry.size_in_bytes != sizeof(T)) { compile_error("Entry size_in_bytes must match sizeof(T)"); @@ -227,7 +246,7 @@ struct MPUDomain { uint32_t offsets_c[3] = {}; // D1, D2, D3 uint32_t assigned_offsets[N]; - for (size_t align : alignments) { + for (size_t align : mpu_supported_alignments) { for (size_t i = 0; i < N; i++) { if (entries[i].alignment == align) { size_t d_idx = static_cast(entries[i].memory_domain) - 1; @@ -260,26 +279,33 @@ struct MPUDomain { void* ptr; std::size_t size; - template - auto& construct(Args&&... args) { - using T = typename std::remove_cvref_t::buffer_type; + template auto& construct(Args&&... args) { + using Request = std::remove_cvref_t; + static_assert(mpu_buffer_request, "Target must be a valid MPUDomain buffer"); + using T = typename Request::buffer_type; return *new (ptr) T(std::forward(args)...); } - template auto* as() { - using T = typename std::remove_cvref_t::buffer_type; + template auto* as() { + using Request = std::remove_cvref_t; + static_assert(mpu_buffer_request, "Target must be a valid MPUDomain buffer"); + using T = typename Request::buffer_type; return static_cast(ptr); } }; - template + template static auto& construct(Args&&... args) { + using Request = std::remove_cvref_t; + static_assert(mpu_buffer_request, "Target must be a valid MPUDomain buffer"); return Board::template instance_of().template construct( std::forward(args)... ); } - template static auto* as() { + template static auto* as() { + using Request = std::remove_cvref_t; + static_assert(mpu_buffer_request, "Target must be a valid MPUDomain buffer"); return Board::template instance_of().template as(); } @@ -289,6 +315,17 @@ struct MPUDomain { static constexpr auto Sizes = calculate_total_sizes(cfgs); // Sections defined in Linker Script (aligned to 32 bytes just in case) +#ifdef SIM_ON + alignas(32 + ) static inline uint8_t d1_nc_buffer[Sizes.d1_nc_total > 0 ? Sizes.d1_nc_total : 1]; + alignas(32) static inline uint8_t d1_c_buffer[Sizes.d1_c_total > 0 ? Sizes.d1_c_total : 1]; + alignas(32 + ) static inline uint8_t d2_nc_buffer[Sizes.d2_nc_total > 0 ? Sizes.d2_nc_total : 1]; + alignas(32) static inline uint8_t d2_c_buffer[Sizes.d2_c_total > 0 ? Sizes.d2_c_total : 1]; + alignas(32 + ) static inline uint8_t d3_nc_buffer[Sizes.d3_nc_total > 0 ? Sizes.d3_nc_total : 1]; + alignas(32) static inline uint8_t d3_c_buffer[Sizes.d3_c_total > 0 ? Sizes.d3_c_total : 1]; +#else __attribute__((section(".mpu_ram_d1_nc.buffer"))) alignas(32 ) static inline uint8_t d1_nc_buffer[Sizes.d1_nc_total > 0 ? Sizes.d1_nc_total : 1]; __attribute__((section(".ram_d1.buffer"))) alignas(32 @@ -303,6 +340,7 @@ struct MPUDomain { ) static inline uint8_t d3_nc_buffer[Sizes.d3_nc_total > 0 ? Sizes.d3_nc_total : 1]; __attribute__((section(".ram_d3.buffer"))) alignas(32 ) static inline uint8_t d3_c_buffer[Sizes.d3_c_total > 0 ? Sizes.d3_c_total : 1]; +#endif static void init() { HAL_MPU_Disable(); @@ -353,8 +391,6 @@ struct MPUDomain { }; private: - static constexpr std::size_t alignments[6] = {32, 16, 8, 4, 2, 1}; - static void configure_dynamic_region(uintptr_t start, uintptr_t end, uint8_t region_num) { if (end <= start) return; diff --git a/Inc/HALAL/Services/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index fc0ea68c0..0c9495780 100644 --- a/Inc/HALAL/Services/DFSDM/DFSDM.hpp +++ b/Inc/HALAL/Services/DFSDM/DFSDM.hpp @@ -1093,17 +1093,21 @@ struct DFSDM_CHANNEL_DOMAIN { // add dma if (cfg.dma_enable == Dma::Enable) { - uint32_t SrcAddress; - if (inst.type_conv == Type_Conversion::Regular) { - SrcAddress = (uint32_t)&filter_hw[inst.filter]->FLTRDATAR; - } else { - SrcAddress = (uint32_t)&filter_hw[inst.filter]->FLTJDATAR; - } - uint32_t DstAddress = - reinterpret_cast(get_buffer_filter(inst.filter) - ); // Transform the pointer to a value - inst.dma_instance - ->start(SrcAddress, DstAddress, buffer_size_for(inst.filter)); + const std::uintptr_t src_address = + inst.type_conv == Type_Conversion::Regular + ? reinterpret_cast( + &filter_hw[inst.filter]->FLTRDATAR + ) + : reinterpret_cast( + &filter_hw[inst.filter]->FLTJDATAR + ); + inst.dma_instance->start( + static_cast(src_address), + static_cast( + reinterpret_cast(get_buffer_filter(inst.filter)) + ), + buffer_size_for(inst.filter) + ); } } // add everything to the channel register From b3fdeded193eaf72cff54c3b6d1b51ce7257d2fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Thu, 23 Apr 2026 19:25:03 +0200 Subject: [PATCH 20/23] fix RTC --- Src/HALAL/Services/Time/RTC.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Src/HALAL/Services/Time/RTC.cpp b/Src/HALAL/Services/Time/RTC.cpp index bade78507..602551f7e 100644 --- a/Src/HALAL/Services/Time/RTC.cpp +++ b/Src/HALAL/Services/Time/RTC.cpp @@ -10,6 +10,11 @@ namespace { bool rtc_started = false; bool rtc_start_in_progress = false; bool rtc_time_valid = false; + +// Match the LSI-backed RTC setup used by STM32H7 Nucleo reference projects. +// 32 kHz / ((127 + 1) * (249 + 1)) = 1 Hz nominal calendar tick. +constexpr uint32_t rtc_async_prediv = 0x7F; +constexpr uint32_t rtc_sync_prediv = 0xF9; } // namespace void Global_RTC::start_rtc() { @@ -23,8 +28,8 @@ void Global_RTC::start_rtc() { hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; - hrtc.Init.AsynchPrediv = 0; - hrtc.Init.SynchPrediv = 32767; + hrtc.Init.AsynchPrediv = rtc_async_prediv; + hrtc.Init.SynchPrediv = rtc_sync_prediv; hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; From fc2ad77ac837d0690373cb606b32df25227ae260 Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sat, 25 Apr 2026 02:44:20 +0200 Subject: [PATCH 21/23] feat(Protections)!:Make protections declared in compile time --- CMakeLists.txt | 1 - Inc/ST-LIB.hpp | 3 - .../Protections/FaultController.hpp | 7 +- Inc/ST-LIB_HIGH/Protections/Protection.hpp | 48 ++- .../Protections/ProtectionEngine.hpp | 287 +++++++++++++----- .../Protections/ProtectionErrors.hpp | 8 - .../Protections/ProtectionEngine.cpp | 88 ------ Tests/CMakeLists.txt | 1 - Tests/TestAccess.hpp | 17 -- Tests/diagnostics_test.cpp | 91 +++--- 10 files changed, 290 insertions(+), 261 deletions(-) delete mode 100644 Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f83042976..ef610e81c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -391,7 +391,6 @@ set(STLIB_HIGH_CPP_NO_ETH ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashStorer.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/FlashStorer/FlashVariable.cpp ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/FaultController.cpp - ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp ) # ============================ diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index 04f7f552a..ef19f73e6 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -313,9 +313,6 @@ template struct Board { cfg.dfsdm_clk_cfgs, GPIODomain::Init::instances ); - - ProtectionEngine::initialize(); - FaultController::start(); } template diff --git a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp index f82ef243f..0cb6fc5f3 100644 --- a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp +++ b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp @@ -8,9 +8,12 @@ namespace ST_LIB::TestAccess { struct FaultController; } +namespace Protections { +template class ProtectionEngine; +} + class PanicReporter; class FaultReporter; -class ProtectionEngine; namespace FaultConfig { inline constexpr size_t origin_capacity = Protections::Config::max_name_length; @@ -91,7 +94,7 @@ class FaultController { private: friend class PanicReporter; friend class FaultReporter; - friend class ProtectionEngine; + template friend class Protections::ProtectionEngine; friend struct ST_LIB::TestAccess::FaultController; enum class RuntimeState : uint8_t { OPERATIONAL = 0, FAULT = 1 }; diff --git a/Inc/ST-LIB_HIGH/Protections/Protection.hpp b/Inc/ST-LIB_HIGH/Protections/Protection.hpp index 2fd74281a..0f3cfced8 100644 --- a/Inc/ST-LIB_HIGH/Protections/Protection.hpp +++ b/Inc/ST-LIB_HIGH/Protections/Protection.hpp @@ -4,7 +4,6 @@ #include "C++Utilities/CppUtils.hpp" #include "HALAL/Services/Time/Scheduler.hpp" -#include "ST-LIB_HIGH/Protections/ProtectionErrors.hpp" #include "ST-LIB_HIGH/Protections/ProtectionTypes.hpp" #include "ST-LIB_HIGH/Protections/Rules.hpp" #include "ST-LIB_HIGH/Protections/SampleSource.hpp" @@ -470,40 +469,26 @@ inline RuleEvaluation evaluate_rule(RuleModel& rule, const T& sample) { ); } -template class Protection { +template class Protection { public: - Protection(const char* name, SampleSource source) : name(name), source(source) {} + Protection( + const char* name, + SampleSource source, + const std::array, RuleCount>& definitions + ) + : name(name), + source(source), + rules(make_rule_models(definitions, std::make_index_sequence{})) {} const char* get_name() const { return name; } void initialize() {} - expected add_rule(expected, RuleConfigError> definition - ) { - if (!definition.has_value()) { - return unexpected(ProtectionError::INVALID_RULE_CONFIGURATION); - } - return add_rule(*definition); - } - - expected add_rule(const RuleDefinition& definition) { - if (rule_count >= Config::max_rules_per_protection) { - return unexpected(ProtectionError::RULE_CAPACITY_EXCEEDED); - } - - rules[rule_count++].emplace(make_rule_model(definition)); - return {}; - } - ProtectionEvaluation evaluate() { ProtectionEvaluation evaluation{}; const T sample = source.read(); - for (size_t index = 0; index < rule_count; ++index) { - if (!rules[index].has_value()) { - continue; - } - - const RuleEvaluation rule_evaluation = evaluate_rule(*rules[index], sample); + for (std::size_t index = 0; index < RuleCount; ++index) { + const RuleEvaluation rule_evaluation = evaluate_rule(rules[index], sample); if (rule_evaluation.edge != RuleEdge::NONE && evaluation.event_count < evaluation.events.size()) { evaluation.events[evaluation.event_count++] = { @@ -537,10 +522,17 @@ template class Protection { void set_last_fault_publish_tick(uint64_t tick) { last_fault_publish_tick = tick; } private: + template + static std::array, RuleCount> make_rule_models( + const std::array, RuleCount>& definitions, + std::index_sequence + ) { + return {make_rule_model(definitions[Indices])...}; + } + const char* name{nullptr}; SampleSource source; - array>, Config::max_rules_per_protection> rules{}; - size_t rule_count{0}; + std::array, RuleCount> rules{}; uint64_t last_fault_publish_tick{0}; }; diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp index 9dea7a083..2d05ccefe 100644 --- a/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp @@ -4,96 +4,245 @@ #include "ST-LIB_HIGH/Protections/FaultController.hpp" #include "ST-LIB_HIGH/Protections/Protection.hpp" -namespace ST_LIB::TestAccess { -struct ProtectionEngine; -} - namespace Protections { -template class ProtectionHandle { -public: - explicit ProtectionHandle(Protection* protection = nullptr) : protection(protection) {} +namespace detail { - expected add_rule(expected, RuleConfigError> definition - ) { - if (protection == nullptr) { - return unexpected(ProtectionError::INVALID_HANDLE); - } - return protection->add_rule(std::move(definition)); +template +concept SampleSourceLike = requires(const std::remove_cvref_t& source) { + typename std::remove_cvref_t::value_type; + source.read(); +}; + +template consteval auto sample_type_tag() { + using source_type = std::remove_cvref_t; + if constexpr (requires { typename source_type::value_type; }) { + return std::type_identity{}; + } else { + return std::type_identity{}; } +} + +template +using sample_type_from_source_t = typename decltype(sample_type_tag())::type; + +template constexpr auto to_sample_source(Source& source) { + if constexpr (SampleSourceLike) { + return source; + } else { + using SampleType = sample_type_from_source_t; + return SampleSource{source}; + } +} + +} // namespace detail + +template struct BakedRules { + using sample_type = T; + static constexpr std::size_t rule_count = N; + + std::array, N> definitions{}; +}; + +template +requires ((std::same_as, RuleDefinition> && ...)) +constexpr auto bake_rules(RuleDefs... definitions) { + return BakedRules{ + std::array, sizeof...(RuleDefs)>{definitions...} + }; +} + +template struct FixedString { + char value[N]{}; - expected add_rule(const RuleDefinition& definition) { - if (protection == nullptr) { - return unexpected(ProtectionError::INVALID_HANDLE); + constexpr FixedString(const char (&str)[N]) { + for (std::size_t index = 0; index < N; ++index) { + value[index] = str[index]; } - return protection->add_rule(definition); } -private: - Protection* protection{nullptr}; + constexpr const char* c_str() const { return value; } + constexpr std::size_t size() const { return N - 1; } }; -using ProtectionVariant = variant< - Protection, - Protection, - Protection, - Protection, - Protection, - Protection, - Protection, - Protection, - Protection, - Protection, - Protection>; +template FixedString(const char (&)[N]) -> FixedString; -} // namespace Protections +template struct ProtectionSpec { + using source_type = std::remove_cvref_t; + using sample_type = detail::sample_type_from_source_t; + + static constexpr auto name = Name; + static constexpr auto& source = Source; +}; + +template struct ProtectionSpecWithRules { + using source_type = std::remove_cvref_t; + using sample_type = detail::sample_type_from_source_t; + using baked_rules_type = std::remove_cvref_t; + + static constexpr auto name = Name; + static constexpr auto& source = Source; + static constexpr auto& baked_rules = Rules; +}; + +template +using ProtectionDeclaration = ProtectionSpec; + +template +using ProtectionDeclarationWithRules = ProtectionSpecWithRules; -class ProtectionEngine { +template class ProtectionEngine { public: - template - static expected< - Protections::ProtectionHandle::value_type>, - Protections::ProtectionError> - create_protection(const char* name, Source source) { - using SampleType = typename std::remove_cvref_t::value_type; - - if (registration_locked) { - return unexpected(Protections::ProtectionError::REGISTRATION_LOCKED); - } - if (protection_count >= Protections::Config::max_protections) { - return unexpected(Protections::ProtectionError::PROTECTION_CAPACITY_EXCEEDED); - } + static constexpr std::size_t protection_count = sizeof...(ProtectionSpecs); + + template + static constexpr bool has_baked_rules = requires { typename Spec::baked_rules_type; }; + + template struct StorageForSpec; + + template + requires has_baked_rules + struct StorageForSpec { + using type = Protection; + }; - auto& slot = protections[protection_count++]; - slot = Protections::ProtectionVariant( - std::in_place_type>, - name, - source - ); - auto* protection = std::get_if>(&slot.value()); - return Protections::ProtectionHandle{protection}; + template + requires (!has_baked_rules) + struct StorageForSpec { + using type = Protection; + }; + + template using storage_for_spec_t = typename StorageForSpec::type; + + using Storage = std::tuple...>; + + static void initialize() { +#if defined(HAL_RTC_MODULE_ENABLED) && !defined(SIM_ON) + Global_RTC::ensure_started(); +#endif + initialize_impl(std::make_index_sequence{}); } - static void initialize(); - static void evaluate(); + static void evaluate() { + evaluate_impl(std::make_index_sequence{}); + } + + template static auto& protection_at() { + return std::get(protections); + } + + template static auto& protection() { + return protection_at()>(); + } + + static void reset() { + reset_impl(std::make_index_sequence{}); + } private: - friend struct ST_LIB::TestAccess::ProtectionEngine; + template static constexpr auto make_protection() { + using SampleType = typename ProtectionSpec::sample_type; + + if constexpr (has_baked_rules) { + using RulesType = typename ProtectionSpec::baked_rules_type; + static_assert( + std::same_as, + "Baked rules sample type must match protection sample type" + ); + + return Protection{ + ProtectionSpec::name.c_str(), + detail::to_sample_source(ProtectionSpec::source), + ProtectionSpec::baked_rules.definitions + }; + } else { + constexpr std::array, 0> empty_rules{}; + return Protection{ + ProtectionSpec::name.c_str(), + detail::to_sample_source(ProtectionSpec::source), + empty_rules + }; + } + } - template - static void request_fault_if_due( - Protection& protection, - const Protections::ProtectionEvaluation& evaluation - ); + template static void initialize_impl(std::index_sequence) { + (std::get(protections).initialize(), ...); + } + + template static void evaluate_impl(std::index_sequence) { + (evaluate_one(), ...); + } + + template static void reset_impl(std::index_sequence) { + (std::get(protections).clear_runtime_state(), ...); + } + + template static void evaluate_one() { + auto& protection_ref = std::get(protections); + const Protections::ProtectionEvaluation evaluation = protection_ref.evaluate(); + + publish_edge_events(protection_ref, evaluation); + if (evaluation.has_active_fault) { + request_fault_if_due(protection_ref, evaluation); + } + } - template + template static void publish_edge_events( - Protection& protection, + ProtectionType& protection_ref, const Protections::ProtectionEvaluation& evaluation - ); + ) { + for (std::size_t event_index = 0; event_index < evaluation.event_count; ++event_index) { + const auto& event = evaluation.events[event_index]; + if (event.state == Protections::RuleState::FAULT) { + continue; + } + + Diagnostics::Hub::publish_protection_event( + protection_ref.get_name(), + event.state, + event.edge, + event.snapshot + ); + } + } + + template + static void request_fault_if_due( + ProtectionType& protection_ref, + const Protections::ProtectionEvaluation& evaluation + ) { + const uint64_t tick = Scheduler::get_global_tick(); + const uint64_t last_publish_tick = protection_ref.get_last_fault_publish_tick(); + + if (last_publish_tick != 0 && + tick < last_publish_tick + Protections::Config::notify_delay_in_microseconds) { + return; + } + + FaultController::request_fault(FaultCause::protection( + protection_ref.get_name(), + evaluation.active_fault_edge, + evaluation.active_fault_snapshot + )); + protection_ref.set_last_fault_publish_tick(tick); + } - static array, Protections::Config::max_protections> - protections; - static size_t protection_count; - static bool registration_locked; + template + static consteval std::size_t spec_index() { + if constexpr (Index >= protection_count) { + static_assert([] { return false; }(), "Protection spec not found"); + return 0; + } else if constexpr (std::is_same_v>>) { + return Index; + } else { + return spec_index(); + } + } + + inline static Storage protections{make_protection()...}; }; + +} // namespace Protections + +using ProtectionEngine = Protections::ProtectionEngine<>; diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp index 64b6bffa2..28e225ff2 100644 --- a/Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp @@ -12,12 +12,4 @@ enum class RuleConfigError : uint8_t { WINDOW_CAPACITY_EXCEEDED, }; -enum class ProtectionError : uint8_t { - INVALID_HANDLE = 0, - INVALID_RULE_CONFIGURATION, - RULE_CAPACITY_EXCEEDED, - PROTECTION_CAPACITY_EXCEEDED, - REGISTRATION_LOCKED, -}; - } // namespace Protections diff --git a/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp b/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp deleted file mode 100644 index f10050af8..000000000 --- a/Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "ST-LIB_HIGH/Protections/ProtectionEngine.hpp" - -#include "HALAL/Services/Time/RTC.hpp" -#include "HALAL/Services/Time/Scheduler.hpp" - -array, Protections::Config::max_protections> - ProtectionEngine::protections = {}; -size_t ProtectionEngine::protection_count = 0; -bool ProtectionEngine::registration_locked = false; - -void ProtectionEngine::initialize() { -#if defined(HAL_RTC_MODULE_ENABLED) && !defined(SIM_ON) - Global_RTC::ensure_started(); -#endif - - registration_locked = true; - for (size_t protection_index = 0; protection_index < protection_count; protection_index++) { - if (protections[protection_index].has_value()) { - visit( - [](auto& protection) { protection.initialize(); }, - *protections[protection_index] - ); - } - } -} - -template -void ProtectionEngine::request_fault_if_due( - Protection& protection, - const Protections::ProtectionEvaluation& evaluation -) { - if (!evaluation.has_active_fault) { - return; - } - - const uint64_t tick = Scheduler::get_global_tick(); - const uint64_t last_publish_tick = protection.get_last_fault_publish_tick(); - if (last_publish_tick != 0 && - tick < last_publish_tick + Protections::Config::notify_delay_in_microseconds) { - return; - } - - FaultController::request_fault(FaultCause::protection( - protection.get_name(), - evaluation.active_fault_edge, - evaluation.active_fault_snapshot - )); - protection.set_last_fault_publish_tick(tick); -} - -template -void ProtectionEngine::publish_edge_events( - Protection& protection, - const Protections::ProtectionEvaluation& evaluation -) { - for (size_t event_index = 0; event_index < evaluation.event_count; event_index++) { - const Protections::ProtectionEvent& event = evaluation.events[event_index]; - if (event.state == Protections::RuleState::FAULT) { - continue; - } - Diagnostics::Hub::publish_protection_event( - protection.get_name(), - event.state, - event.edge, - event.snapshot - ); - } -} - -void ProtectionEngine::evaluate() { - for (size_t protection_index = 0; protection_index < protection_count; protection_index++) { - if (!protections[protection_index].has_value()) { - continue; - } - - visit( - [](auto& protection) { - const Protections::ProtectionEvaluation evaluation = protection.evaluate(); - ProtectionEngine::publish_edge_events(protection, evaluation); - - if (evaluation.has_active_fault) { - ProtectionEngine::request_fault_if_due(protection, evaluation); - } - }, - *protections[protection_index] - ); - } -} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 0a41fa5b8..f2e81e4d4 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -28,7 +28,6 @@ add_executable(${STLIB_TEST_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Models/DMA/DMA2.cpp ${CMAKE_CURRENT_LIST_DIR}/../Src/HALAL/Services/DFSDM/DFSDM.cpp ${CMAKE_CURRENT_LIST_DIR}/../Src/ST-LIB_HIGH/Protections/FaultController.cpp - ${CMAKE_CURRENT_LIST_DIR}/../Src/ST-LIB_HIGH/Protections/ProtectionEngine.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/encoder_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/scheduler_test.cpp ${CMAKE_CURRENT_LIST_DIR}/Time/timer_wrapper_test.cpp diff --git a/Tests/TestAccess.hpp b/Tests/TestAccess.hpp index 1e4fa4ae9..8a31a15e2 100644 --- a/Tests/TestAccess.hpp +++ b/Tests/TestAccess.hpp @@ -23,23 +23,6 @@ struct DiagnosticsHub { static size_t pending_size() { return Diagnostics::Hub::pending_count; } }; -struct ProtectionEngine { - static void clear() { - for (size_t protection_index = 0; protection_index < ::ProtectionEngine::protection_count; - ++protection_index) { - if (::ProtectionEngine::protections[protection_index].has_value()) { - visit( - [](auto& protection) { protection.clear_runtime_state(); }, - *::ProtectionEngine::protections[protection_index] - ); - ::ProtectionEngine::protections[protection_index].reset(); - } - } - ::ProtectionEngine::protection_count = 0; - ::ProtectionEngine::registration_locked = false; - } -}; - struct FaultController { static void clear() { ::FaultController::reset_runtime_storage(); diff --git a/Tests/diagnostics_test.cpp b/Tests/diagnostics_test.cpp index 9195f0ddc..f5bf74ac6 100644 --- a/Tests/diagnostics_test.cpp +++ b/Tests/diagnostics_test.cpp @@ -76,6 +76,30 @@ void reset_operational_machine() { void on_fault_enter() { fault_enter_calls++; } +inline float monitored_value = 2.0f; +inline constexpr auto monitored_rules = Protections::bake_rules( + Protections::Rules::below(1.0f, 1.5f).value() +); +using MonitoredProtection = + Protections::ProtectionDeclarationWithRules<"monitored_value", monitored_value, monitored_rules>; +using MonitoredProtectionEngine = Protections::ProtectionEngine; + +inline float time_value = 0.0f; +inline constexpr auto time_rules = Protections::bake_rules( + Protections::Rules::time_accumulation(10.0f, 0.001f).value() +); +using TimeProtection = + Protections::ProtectionDeclarationWithRules<"time_value", time_value, time_rules>; +using TimeProtectionEngine = Protections::ProtectionEngine; + +inline float time_reset_value = 0.0f; +inline constexpr auto time_reset_rules = Protections::bake_rules( + Protections::Rules::time_accumulation(10.0f, 0.001f).value() +); +using TimeResetProtection = + Protections::ProtectionDeclarationWithRules<"time_reset_value", time_reset_value, time_reset_rules>; +using TimeResetProtectionEngine = Protections::ProtectionEngine; + FaultCause make_test_runtime_fault(const char* message) { return FaultCause::runtime_fault(message, false, 0, "diagnostics_test", "diagnostics_test.cpp"); } @@ -101,7 +125,9 @@ class DiagnosticsHubTest : public ::testing::Test { protected: void SetUp() override { TestAccess::DiagnosticsHub::clear(); - TestAccess::ProtectionEngine::clear(); + MonitoredProtectionEngine::reset(); + TimeProtectionEngine::reset(); + TimeResetProtectionEngine::reset(); TestAccess::FaultController::clear(); reset_operational_machine(); TestPanicReporter::reset(); @@ -263,7 +289,9 @@ TEST_F(DiagnosticsHubTest, FaultControllerStopsDelegatingAfterFault) { TEST(DiagnosticsBootstrapTest, PanicBeforeRuntimeInstallationSurvivesBootstrapAndIsDelivered) { TestAccess::DiagnosticsHub::clear(); - TestAccess::ProtectionEngine::clear(); + MonitoredProtectionEngine::reset(); + TimeProtectionEngine::reset(); + TimeResetProtectionEngine::reset(); TestAccess::FaultController::clear(); reset_operational_machine(); TestPanicReporter::reset(); @@ -296,20 +324,13 @@ TEST(DiagnosticsBootstrapTest, PanicBeforeRuntimeInstallationSurvivesBootstrapAn } TEST_F(DiagnosticsHubTest, ProtectionEngineEvaluatesRulesAndPublishesSnapshots) { - float monitored_value = 2.0f; - SampleSource source(monitored_value); - auto sink_result = Diagnostics::Hub::emplace_sink(); ASSERT_TRUE(sink_result.has_value()); auto* sink = *sink_result; - auto protection = ProtectionEngine::create_protection("monitored_value", source); - ASSERT_TRUE(protection.has_value()); - ASSERT_TRUE(protection->add_rule(Protections::Rules::below(1.0f, 1.5f)).has_value()); - - ProtectionEngine::initialize(); + MonitoredProtectionEngine::initialize(); monitored_value = 0.5f; - ProtectionEngine::evaluate(); + MonitoredProtectionEngine::evaluate(); Diagnostics::Hub::flush(); ASSERT_FALSE(sink->records.empty()); @@ -322,32 +343,23 @@ TEST_F(DiagnosticsHubTest, ProtectionEngineEvaluatesRulesAndPublishesSnapshots) } TEST_F(DiagnosticsHubTest, TimeAccumulationUsesSchedulerTickForContinuousDuration) { - float monitored_value = 0.0f; - SampleSource source(monitored_value); - auto sink_result = Diagnostics::Hub::emplace_sink(); ASSERT_TRUE(sink_result.has_value()); auto* sink = *sink_result; - auto protection = ProtectionEngine::create_protection("time_value", source); - ASSERT_TRUE(protection.has_value()); - ASSERT_TRUE( - protection->add_rule(Protections::Rules::time_accumulation(10.0f, 0.001f)).has_value() - ); + TimeProtectionEngine::initialize(); - ProtectionEngine::initialize(); - - monitored_value = 12.0f; + time_value = 12.0f; Scheduler::global_tick_us_ = 0; - ProtectionEngine::evaluate(); + TimeProtectionEngine::evaluate(); EXPECT_FALSE(FaultController::is_faulted()); Scheduler::global_tick_us_ = 500; - ProtectionEngine::evaluate(); + TimeProtectionEngine::evaluate(); EXPECT_FALSE(FaultController::is_faulted()); Scheduler::global_tick_us_ = 1'000; - ProtectionEngine::evaluate(); + TimeProtectionEngine::evaluate(); Diagnostics::Hub::flush(); ASSERT_FALSE(sink->records.empty()); @@ -362,40 +374,31 @@ TEST_F(DiagnosticsHubTest, TimeAccumulationUsesSchedulerTickForContinuousDuratio } TEST_F(DiagnosticsHubTest, TimeAccumulationResetsWhenConditionClears) { - float monitored_value = 0.0f; - SampleSource source(monitored_value); - - auto protection = ProtectionEngine::create_protection("time_reset_value", source); - ASSERT_TRUE(protection.has_value()); - ASSERT_TRUE( - protection->add_rule(Protections::Rules::time_accumulation(10.0f, 0.001f)).has_value() - ); - - ProtectionEngine::initialize(); + TimeResetProtectionEngine::initialize(); - monitored_value = 12.0f; + time_value = 12.0f; Scheduler::global_tick_us_ = 0; - ProtectionEngine::evaluate(); + TimeResetProtectionEngine::evaluate(); Scheduler::global_tick_us_ = 700; - ProtectionEngine::evaluate(); + TimeResetProtectionEngine::evaluate(); EXPECT_FALSE(FaultController::is_faulted()); - monitored_value = 0.0f; + time_reset_value = 0.0f; Scheduler::global_tick_us_ = 800; - ProtectionEngine::evaluate(); + TimeResetProtectionEngine::evaluate(); EXPECT_FALSE(FaultController::is_faulted()); Scheduler::global_tick_us_ = 1'600; - ProtectionEngine::evaluate(); + TimeResetProtectionEngine::evaluate(); EXPECT_FALSE(FaultController::is_faulted()); - monitored_value = 12.0f; + time_reset_value = 12.0f; Scheduler::global_tick_us_ = 1'600; - ProtectionEngine::evaluate(); + TimeResetProtectionEngine::evaluate(); Scheduler::global_tick_us_ = 2'300; - ProtectionEngine::evaluate(); + TimeResetProtectionEngine::evaluate(); EXPECT_FALSE(FaultController::is_faulted()); } From 922ef0a062b28c04cf7661a3d1310c43ece36afc Mon Sep 17 00:00:00 2001 From: FoniksFox Date: Sat, 25 Apr 2026 02:50:01 +0200 Subject: [PATCH 22/23] fix(SPI Tests): Reset SPI entirely on each init so that tests can be happy --- Inc/HALAL/Models/SPI/SPI2.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index c3f166f17..89af652ac 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -1272,9 +1272,13 @@ struct SPIDomain { for (std::size_t i = 0; i < N; ++i) { const auto& e = cfgs[i]; + SPIPeripheral peripheral = e.peripheral; instances[i].instance = reinterpret_cast(e.peripheral); - + instances[i].operation_flag = nullptr; + instances[i].error_count = 0; + instances[i].was_aborted = false; + // Configure clock and store handle RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; uint8_t spi_number = 0; From 5309421b0aae902a30f6101593cee7805befe0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20S=C3=A1ez?= Date: Mon, 27 Apr 2026 20:24:15 +0200 Subject: [PATCH 23/23] feat(Protections)!: integrate compile-time protections with Board --- .changesets/pm-no-eth-major.md | 5 +- CMakeLists.txt | 11 ++ Inc/HALAL/Models/SPI/SPI2.hpp | 3 +- Inc/ST-LIB.hpp | 36 +++++ .../Protections/FaultController.hpp | 4 +- Inc/ST-LIB_HIGH/Protections/Protection.hpp | 7 +- .../Protections/ProtectionEngine.hpp | 132 ++++++++++-------- .../board_protection_contract.cpp | 23 +++ Tests/diagnostics_test.cpp | 28 ++-- docs/protections-and-diagnostics.md | 84 +++++------ docs/st-lib-board-contract.md | 17 ++- 11 files changed, 215 insertions(+), 135 deletions(-) create mode 100644 Tests/compile_checks/board_protection_contract.cpp diff --git a/.changesets/pm-no-eth-major.md b/.changesets/pm-no-eth-major.md index e7a11f6d2..0edcccff8 100644 --- a/.changesets/pm-no-eth-major.md +++ b/.changesets/pm-no-eth-major.md @@ -8,7 +8,7 @@ Breaking changes: - `Board` now takes the fault policy type as its first template parameter. - The global `FAULT` runtime is owned exclusively by `FaultController`. - User state machines are now nested under the global `OPERATIONAL` state through `FaultPolicy` or `FaultPolicyNoMachine`. -- Protections now use `ProtectionEngine` and `Protections::Rules::*`; the previous `ProtectionManager` and boundary split is no longer the active model. +- Protections are now compile-time `Board` request objects evaluated through `Board::ProtectionEngine`; the previous `ProtectionManager` and boundary split is no longer the active model. - Runtime reporting is unified under `PANIC(...)`, `FAULT(...)`, `WARNING(...)`, and `INFO(...)`. - The real bootstrap path is `Board::init()`. Legacy `STLIB::start()`, `STLIB::update()`, `STLIB_LOW::start()`, and `STLIB_HIGH::start()` must not be used as the integration path. @@ -18,4 +18,5 @@ Migration notes: - Use `FaultPolicy` when you want an operational state machine nested under the global runtime. - Use `FaultPolicyNoMachine` when you only need a fault-entry callback. - Use `DefaultFaultPolicy` when you want neither an operational machine nor a fault-entry callback. -- In the main loop, drive the runtime through `FaultController::check_transitions()`, `ProtectionEngine::evaluate()`, and `Diagnostics::Hub::flush()`. +- Declare protections with `Protections::protection<"name", source>(...)` and pass the resulting request objects to `Board`. +- In the main loop, drive the runtime through `FaultController::check_transitions()`, `Board::evaluate_protections()`, and `Diagnostics::Hub::flush()`. diff --git a/CMakeLists.txt b/CMakeLists.txt index ef610e81c..f4d629e3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -564,6 +564,17 @@ else() endif() endif() +if(CMAKE_CROSSCOMPILING) + add_library(stlib_compile_check_board_protections OBJECT + ${CMAKE_CURRENT_LIST_DIR}/Tests/compile_checks/board_protection_contract.cpp + ) + target_link_libraries(stlib_compile_check_board_protections PRIVATE ${STLIB_LIBRARY}) + set_target_properties(stlib_compile_check_board_protections PROPERTIES + CXX_STANDARD 23 + CXX_STANDARD_REQUIRED YES + ) +endif() + if(PROJECT_IS_TOP_LEVEL) execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index 89af652ac..4b802c425 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -1272,13 +1272,12 @@ struct SPIDomain { for (std::size_t i = 0; i < N; ++i) { const auto& e = cfgs[i]; - SPIPeripheral peripheral = e.peripheral; instances[i].instance = reinterpret_cast(e.peripheral); instances[i].operation_flag = nullptr; instances[i].error_count = 0; instances[i].was_aborted = false; - + // Configure clock and store handle RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; uint8_t spi_number = 0; diff --git a/Inc/ST-LIB.hpp b/Inc/ST-LIB.hpp index ef19f73e6..0a43afce1 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -141,10 +141,37 @@ consteval std::array build_dma_configs( }); } +template struct ProtectionRequestRef { + static constexpr auto& value = Request; +}; + +template consteval auto protection_spec_tuple() { + using RequestT = std::remove_cvref_t; + + if constexpr (Protections::ProtectionSpecLike) { + return std::tuple>{}; + } else { + return std::tuple<>{}; + } +} + +template struct ProtectionEngineFromTuple; + +template +struct ProtectionEngineFromTuple> { + using type = Protections::ProtectionEngine; +}; + +template +using ProtectionEngineForRequests = typename ProtectionEngineFromTuple< + decltype(std::tuple_cat(protection_spec_tuple()...))>::type; + } // namespace BuildUtils template struct Board { public: + using ProtectionEngine = BuildUtils::ProtectionEngineForRequests; + static consteval auto build_ctx() { DomainsCtx ctx{}; (devs.inscribe(ctx), ...); @@ -313,6 +340,15 @@ template struct Board { cfg.dfsdm_clk_cfgs, GPIODomain::Init::instances ); + + ProtectionEngine::initialize(); + FaultController::start(); + } + + static void evaluate_protections() { ProtectionEngine::evaluate(); } + + template static auto& protection() { + return ProtectionEngine::template protection(); } template diff --git a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp index 0cb6fc5f3..bcf65adde 100644 --- a/Inc/ST-LIB_HIGH/Protections/FaultController.hpp +++ b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp @@ -9,7 +9,7 @@ struct FaultController; } namespace Protections { -template class ProtectionEngine; +template class ProtectionEngine; } class PanicReporter; @@ -94,7 +94,7 @@ class FaultController { private: friend class PanicReporter; friend class FaultReporter; - template friend class Protections::ProtectionEngine; + template friend class Protections::ProtectionEngine; friend struct ST_LIB::TestAccess::FaultController; enum class RuntimeState : uint8_t { OPERATIONAL = 0, FAULT = 1 }; diff --git a/Inc/ST-LIB_HIGH/Protections/Protection.hpp b/Inc/ST-LIB_HIGH/Protections/Protection.hpp index 0f3cfced8..17865f8c1 100644 --- a/Inc/ST-LIB_HIGH/Protections/Protection.hpp +++ b/Inc/ST-LIB_HIGH/Protections/Protection.hpp @@ -478,6 +478,7 @@ template class Protection { ) : name(name), source(source), + definitions(definitions), rules(make_rule_models(definitions, std::make_index_sequence{})) {} const char* get_name() const { return name; } @@ -515,7 +516,10 @@ template class Protection { return evaluation; } - void clear_runtime_state() { last_fault_publish_tick = 0; } + void clear_runtime_state() { + rules = make_rule_models(definitions, std::make_index_sequence{}); + last_fault_publish_tick = 0; + } uint64_t get_last_fault_publish_tick() const { return last_fault_publish_tick; } @@ -532,6 +536,7 @@ template class Protection { const char* name{nullptr}; SampleSource source; + std::array, RuleCount> definitions{}; std::array, RuleCount> rules{}; uint64_t last_fault_publish_tick{0}; }; diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp index 2d05ccefe..1c3b6dce2 100644 --- a/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp @@ -35,8 +35,6 @@ template constexpr auto to_sample_source(Source& source) { } } -} // namespace detail - template struct BakedRules { using sample_type = T; static constexpr std::size_t rule_count = N; @@ -44,14 +42,41 @@ template struct BakedRules { std::array, N> definitions{}; }; +template +concept RuleDefinitionLike = + ProtectionSample && ( + std::same_as, RuleDefinition> || + std::same_as, expected, RuleConfigError>> + ); + +template constexpr RuleDefinition unwrap_rule(RuleDefinition rule) { + return rule; +} + +template +constexpr RuleDefinition unwrap_rule(expected, RuleConfigError> rule) { + return rule.value(); +} + template -requires ((std::same_as, RuleDefinition> && ...)) +requires ((RuleDefinitionLike && ...)) constexpr auto bake_rules(RuleDefs... definitions) { + static_assert(sizeof...(RuleDefs) > 0, "A protection must declare at least one rule"); return BakedRules{ - std::array, sizeof...(RuleDefs)>{definitions...} + std::array, sizeof...(RuleDefs)>{ + unwrap_rule(definitions)... + } }; } +template struct AreUnique : std::true_type {}; + +template +struct AreUnique + : std::bool_constant<(!std::same_as && ...) && AreUnique::value> {}; + +} // namespace detail + template struct FixedString { char value[N]{}; @@ -67,52 +92,50 @@ template struct FixedString { template FixedString(const char (&)[N]) -> FixedString; -template struct ProtectionSpec { +template struct ProtectionSpec { using source_type = std::remove_cvref_t; using sample_type = detail::sample_type_from_source_t; static constexpr auto name = Name; static constexpr auto& source = Source; -}; + static constexpr std::size_t rule_count = RuleCount; -template struct ProtectionSpecWithRules { - using source_type = std::remove_cvref_t; - using sample_type = detail::sample_type_from_source_t; - using baked_rules_type = std::remove_cvref_t; + detail::BakedRules rules{}; - static constexpr auto name = Name; - static constexpr auto& source = Source; - static constexpr auto& baked_rules = Rules; + template consteval void inscribe(Ctx&) const {} }; -template -using ProtectionDeclaration = ProtectionSpec; +template +consteval auto protection(RuleDefs... definitions) { + using SampleType = detail::sample_type_from_source_t; + return ProtectionSpec{ + detail::bake_rules(definitions...) + }; +} -template -using ProtectionDeclarationWithRules = ProtectionSpecWithRules; +template struct IsProtectionSpec : std::false_type {}; -template class ProtectionEngine { -public: - static constexpr std::size_t protection_count = sizeof...(ProtectionSpecs); +template +struct IsProtectionSpec> : std::true_type {}; - template - static constexpr bool has_baked_rules = requires { typename Spec::baked_rules_type; }; +template +concept ProtectionSpecLike = IsProtectionSpec>::value; - template struct StorageForSpec; +template class ProtectionEngine { +public: + static_assert( + detail::AreUnique...>::value, + "Duplicate protection declarations must use distinct names or sources" + ); - template - requires has_baked_rules - struct StorageForSpec { - using type = Protection; - }; + static constexpr std::size_t protection_count = sizeof...(ProtectionSpecs); - template - requires (!has_baked_rules) - struct StorageForSpec { - using type = Protection; + template struct StorageForSpec { + using spec_type = std::remove_cvref_t; + using type = Protection; }; - template using storage_for_spec_t = typename StorageForSpec::type; + template using storage_for_spec_t = typename StorageForSpec::type; using Storage = std::tuple...>; @@ -120,6 +143,7 @@ template class ProtectionEngine { #if defined(HAL_RTC_MODULE_ENABLED) && !defined(SIM_ON) Global_RTC::ensure_started(); #endif + reset(); initialize_impl(std::make_index_sequence{}); } @@ -131,7 +155,7 @@ template class ProtectionEngine { return std::get(protections); } - template static auto& protection() { + template static auto& protection() { return protection_at()>(); } @@ -140,29 +164,15 @@ template class ProtectionEngine { } private: - template static constexpr auto make_protection() { - using SampleType = typename ProtectionSpec::sample_type; - - if constexpr (has_baked_rules) { - using RulesType = typename ProtectionSpec::baked_rules_type; - static_assert( - std::same_as, - "Baked rules sample type must match protection sample type" - ); - - return Protection{ - ProtectionSpec::name.c_str(), - detail::to_sample_source(ProtectionSpec::source), - ProtectionSpec::baked_rules.definitions - }; - } else { - constexpr std::array, 0> empty_rules{}; - return Protection{ - ProtectionSpec::name.c_str(), - detail::to_sample_source(ProtectionSpec::source), - empty_rules - }; - } + template static constexpr auto make_protection() { + using SpecType = std::remove_cvref_t; + using SampleType = typename SpecType::sample_type; + + return Protection{ + SpecType::name.c_str(), + detail::to_sample_source(SpecType::source), + ProtectionSpec.rules.definitions + }; } template static void initialize_impl(std::index_sequence) { @@ -228,12 +238,14 @@ template class ProtectionEngine { protection_ref.set_last_fault_publish_tick(tick); } - template + template static consteval std::size_t spec_index() { if constexpr (Index >= protection_count) { static_assert([] { return false; }(), "Protection spec not found"); return 0; - } else if constexpr (std::is_same_v>>) { + } else if constexpr (std::same_as< + std::remove_cvref_t, + std::remove_cvref_t(std::tie(ProtectionSpecs...)))>>) { return Index; } else { return spec_index(); @@ -244,5 +256,3 @@ template class ProtectionEngine { }; } // namespace Protections - -using ProtectionEngine = Protections::ProtectionEngine<>; diff --git a/Tests/compile_checks/board_protection_contract.cpp b/Tests/compile_checks/board_protection_contract.cpp new file mode 100644 index 000000000..172d198a5 --- /dev/null +++ b/Tests/compile_checks/board_protection_contract.cpp @@ -0,0 +1,23 @@ +#include "ST-LIB.hpp" + +namespace { + +float protected_value = 1.0f; + +inline constexpr auto protected_value_protection = + Protections::protection<"protected_value", protected_value>( + Protections::Rules::above(10.0f) + ); + +using ContractBoard = ST_LIB::Board; + +static_assert(ContractBoard::ProtectionEngine::protection_count == 1); +static_assert(std::same_as< + decltype(ContractBoard::protection()), + Protections::Protection&>); + +void compile_board_protection_contract() { + ContractBoard::evaluate_protections(); +} + +} // namespace diff --git a/Tests/diagnostics_test.cpp b/Tests/diagnostics_test.cpp index f5bf74ac6..be02475d6 100644 --- a/Tests/diagnostics_test.cpp +++ b/Tests/diagnostics_test.cpp @@ -77,28 +77,24 @@ void reset_operational_machine() { void on_fault_enter() { fault_enter_calls++; } inline float monitored_value = 2.0f; -inline constexpr auto monitored_rules = Protections::bake_rules( - Protections::Rules::below(1.0f, 1.5f).value() +inline constexpr auto monitored_protection = Protections::protection<"monitored_value", monitored_value>( + Protections::Rules::below(1.0f, 1.5f) ); -using MonitoredProtection = - Protections::ProtectionDeclarationWithRules<"monitored_value", monitored_value, monitored_rules>; -using MonitoredProtectionEngine = Protections::ProtectionEngine; +using MonitoredProtectionEngine = Protections::ProtectionEngine; inline float time_value = 0.0f; -inline constexpr auto time_rules = Protections::bake_rules( - Protections::Rules::time_accumulation(10.0f, 0.001f).value() +inline constexpr auto time_protection = Protections::protection<"time_value", time_value>( + Protections::Rules::time_accumulation(10.0f, 0.001f) ); -using TimeProtection = - Protections::ProtectionDeclarationWithRules<"time_value", time_value, time_rules>; -using TimeProtectionEngine = Protections::ProtectionEngine; +using TimeProtectionEngine = Protections::ProtectionEngine; inline float time_reset_value = 0.0f; -inline constexpr auto time_reset_rules = Protections::bake_rules( - Protections::Rules::time_accumulation(10.0f, 0.001f).value() +inline constexpr auto time_reset_protection = Protections::protection<"time_reset_value", time_reset_value>( + Protections::Rules::time_accumulation(10.0f, 0.001f) ); -using TimeResetProtection = - Protections::ProtectionDeclarationWithRules<"time_reset_value", time_reset_value, time_reset_rules>; -using TimeResetProtectionEngine = Protections::ProtectionEngine; +using TimeResetProtectionEngine = Protections::ProtectionEngine; + +static_assert(Protections::ProtectionSpecLike); FaultCause make_test_runtime_fault(const char* message) { return FaultCause::runtime_fault(message, false, 0, "diagnostics_test", "diagnostics_test.cpp"); @@ -376,7 +372,7 @@ TEST_F(DiagnosticsHubTest, TimeAccumulationUsesSchedulerTickForContinuousDuratio TEST_F(DiagnosticsHubTest, TimeAccumulationResetsWhenConditionClears) { TimeResetProtectionEngine::initialize(); - time_value = 12.0f; + time_reset_value = 12.0f; Scheduler::global_tick_us_ = 0; TimeResetProtectionEngine::evaluate(); diff --git a/docs/protections-and-diagnostics.md b/docs/protections-and-diagnostics.md index ecfc3c908..a7601e0cc 100644 --- a/docs/protections-and-diagnostics.md +++ b/docs/protections-and-diagnostics.md @@ -17,7 +17,7 @@ If you only want to integrate protections into an application, read the first pa The subsystem has three explicit runtime operations: - `Board::init()` -- `ProtectionEngine::evaluate()` +- `Board::ProtectionEngine::evaluate()` or `Board::evaluate_protections()` - `Diagnostics::Hub::flush()` If the application uses an operational state machine nested under the global runtime, it also @@ -34,11 +34,11 @@ The global fault model is always the same: ```mermaid flowchart TD - A["Register protections"] --> B["Declare Board policy and request objects"] + A["Declare protection rules"] --> B["Declare Board policy and request objects"] B --> C["Board::init()"] C --> D["while (1)"] D --> E["FaultController::check_transitions()"] - D --> F["ProtectionEngine::evaluate()"] + D --> F["Board::evaluate_protections()"] D --> G["Diagnostics::Hub::flush()"] ``` @@ -51,21 +51,21 @@ Board Where: - `FaultPolicyT` is mandatory and is always the first template argument -- `dev0, dev1, ...` are the board declarations to inscribe into the domains +- `dev0, dev1, ...` are board declarations, including hardware requests and protection requests - the framework always owns the top-level runtime machine - the application may optionally provide a nested operational machine and/or a `FAULT` entry callback -### 1.2 Registering Protections +### 1.2 Declaring Protections -Protections are created through `ProtectionEngine::create_protection(...)`. +Protections are compile-time board requests. A protection request: -Each protection: +- has a stable name encoded in the type +- reads from one sample source object or sample variable +- owns a fixed set of rules declared before runtime +- is passed to `Board<...>` with the rest of the board request objects -- has a stable name -- reads from one `SampleSource` -- owns one or more rules - -Rules are added through the factories in `Protections::Rules`. +Rules are created through the factories in `Protections::Rules` and passed to +`Protections::protection<"name", source>(...)`. Available rule factories: @@ -76,8 +76,9 @@ Available rule factories: - `Rules::not_equals(...)` - `Rules::time_accumulation(...)` -Both `create_protection(...)` and `add_rule(...)` return `std::expected`, so configuration errors -must be handled explicitly. +Rule factories return `std::expected`; `Protections::protection(...)` unwraps them while building +the compile-time declaration. Invalid declarations fail during build or constant evaluation instead +of creating a partial runtime registry. Current rule signatures are: @@ -106,21 +107,19 @@ Rules::time_accumulation(fault_threshold, warning_threshold, window_seconds) - it resets the accumulated active time when the triggering condition clears - it uses `Scheduler::get_global_tick()`, so it does not depend on the `while (1)` iteration rate -### 1.3 When to Register Protections +### 1.3 Protection Lifecycle -Register protections before `Board::init()`. +Declare protections at namespace scope and pass them to `Board`. The intended lifecycle is: -1. registration +1. compile-time declaration 2. `Board::init()` 3. evaluation and flushing in the runtime loop -After `Board::init()`, the protection registry is locked. - -If registration code reports a fatal condition before `Board::init()`, that fatal request is still -preserved across the first fault-runtime installation and its diagnostic record remains eligible for -later delivery once sinks are installed. +There is no runtime registration phase and no mutable protection registry. `Board` derives a +board-specific `ProtectionEngine` type from the protection requests it receives, initializes it from +`Board::init()`, and then starts the global fault runtime. ### 1.4 Typical Protection Example @@ -130,35 +129,21 @@ later delivery once sinks are installed. using namespace ST_LIB; constexpr auto led = DigitalOutputDomain::DigitalOutput(PF13); -using MainBoard = Board; float bus_voltage = 0.0f; -int main() { - auto protection = ProtectionEngine::create_protection( - "bus_voltage", - SampleSource{bus_voltage} - ); - - if (!protection.has_value()) { - PANIC("failed to register bus_voltage protection"); - } +inline constexpr auto bus_voltage_protection = Protections::protection<"bus_voltage", bus_voltage>( + Protections::Rules::below(350.0f, 370.0f), + Protections::Rules::time_accumulation(20.0f, 15.0f, 0.5f) +); - if (!protection->add_rule(Protections::Rules::below(350.0f, 370.0f)).has_value()) { - PANIC("failed to add below rule"); - } - - if (!protection->add_rule( - Protections::Rules::time_accumulation(20.0f, 15.0f, 0.5f) - ) - .has_value()) { - PANIC("failed to add time_accumulation rule"); - } +using MainBoard = Board; +int main() { MainBoard::init(); while (1) { - ProtectionEngine::evaluate(); + MainBoard::evaluate_protections(); Diagnostics::Hub::flush(); } } @@ -205,7 +190,7 @@ int main() { while (1) { FaultController::check_transitions(); - ProtectionEngine::evaluate(); + MainBoard::evaluate_protections(); Diagnostics::Hub::flush(); } } @@ -346,13 +331,14 @@ The key boundaries are: Public API: -- `ProtectionEngine::create_protection(...)` -- `ProtectionHandle::add_rule(...)` +- `Protections::protection<"name", source>(...)` +- `Board::ProtectionEngine` +- `Board::evaluate_protections()` - `Protections::Rules::*` Internal model: -- one flat collection of protections +- one board-specific compile-time collection of protections - no low/high frequency split in the domain model - rule configuration returned through `std::expected` - rule evaluation produces `RuleState`, `RuleEdge`, and `RuleSnapshot` @@ -369,7 +355,7 @@ Supported rule kinds: `TIME_ACCUMULATION` uses `Scheduler::get_global_tick()` to measure real elapsed time. It no longer assumes a fixed evaluation rate. -`ProtectionEngine::evaluate()`: +`Board::ProtectionEngine::evaluate()`: - walks every protection - publishes non-fatal rule edges through `Diagnostics` @@ -502,7 +488,7 @@ This avoids recursive or bootstrap-dependent fatal paths while timestamping diag This subsystem uses a narrow set of C++23 features where they provide direct value: - `std::expected` - for explicit registration/configuration failure + for explicit rule configuration failure - `std::variant` and `std::visit` for static rule composition - `concepts` diff --git a/docs/st-lib-board-contract.md b/docs/st-lib-board-contract.md index 68d1dc728..0575a8a95 100644 --- a/docs/st-lib-board-contract.md +++ b/docs/st-lib-board-contract.md @@ -16,6 +16,9 @@ If you change a domain, add a new domain, or add a cross-domain composition rule into concrete configs. The first template argument is not a request object: it is the global fault runtime policy type used by `FaultController`. +Protection declarations are also request objects. They do not inscribe hardware into `BuildCtx`; +instead, `Board` filters them into a board-specific `ProtectionEngine` type. + ## 1.1 Board Policy Contract The first template argument of `Board` must be a fault policy type. @@ -52,8 +55,7 @@ signature. A request object that can be used after the first `Policy` argument inside `Board` must provide: -- `using domain = ;` -- `template consteval std::size_t inscribe(Ctx&) const` +- `template consteval ... inscribe(Ctx&) const` or any compatible return type if it naturally inscribes multiple dependent entries. @@ -64,6 +66,10 @@ or any compatible return type if it naturally inscribes multiple dependent entri - return indices only for later compile-time wiring - never depend on runtime state +Hardware requests also expose `using domain = ;` for `Board::instance_of()`. +Protection requests intentionally do not expose a hardware domain and are accessed through +`Board::ProtectionEngine`. + ## 4. BuildCtx Contract `BuildCtx` is append-only. @@ -93,6 +99,9 @@ This is a deliberate design choice. `BuildCtx` is a storage and ownership map, n `Board::cfg` is the compile-time result of that process. +`Board::ProtectionEngine` is the compile-time protection engine assembled from the protection +request objects in the same `Board` declaration. + No runtime-only configuration logic belongs here. ## 6. Board Init Contract @@ -110,6 +119,8 @@ Permitted runtime work: - clock enable - IRQ enable - handle linking +- initializing the board-specific protection engine +- starting the global fault runtime - buffer allocation if the buffer is inherently runtime memory - starting peripherals using already-built configs @@ -206,5 +217,7 @@ When reviewing a change to `Board`, a domain, or a DMA-using peripheral, verify: - Does the request object inscribe only compile-time information? - Can duplicate physical resources be produced? If yes, where are they prevented? - Does runtime init consume only `cfg`? +- Are protection requests passed through the board and evaluated through `Board::ProtectionEngine` + or `Board::evaluate_protections()`? - Is any stream/request/allocation decision being made too late? - If the domain uses shared DMA, is it contributing only missing entries and preserving the rest?