diff --git a/.changesets/pm-no-eth-major.md b/.changesets/pm-no-eth-major.md new file mode 100644 index 000000000..0edcccff8 --- /dev/null +++ b/.changesets/pm-no-eth-major.md @@ -0,0 +1,22 @@ +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 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. + +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. +- 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 409c22148..f4d629e3e 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 ) @@ -368,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 @@ -380,17 +383,14 @@ 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/ST-LIB_HIGH.cpp + ${CMAKE_CURRENT_LIST_DIR}/Src/ST-LIB_HIGH/Protections/FaultController.cpp ) # ============================ @@ -426,9 +426,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> @@ -567,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/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/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/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/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/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/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..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) { - ErrorHandler( - "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) { - ErrorHandler( - "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 02b1433fd..4b802c425 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,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) { - ErrorHandler( - "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) { - ErrorHandler( - "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) { - ErrorHandler( - "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) { - ErrorHandler( - "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( @@ -784,7 +768,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,11 +793,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - ErrorHandler( - "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( @@ -832,7 +812,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,11 +837,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - ErrorHandler( - "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) { - ErrorHandler( - "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) { - ErrorHandler( - "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) { - ErrorHandler( - "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) { - ErrorHandler( - "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( @@ -994,7 +954,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 +1008,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,11 +1033,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - ErrorHandler( - "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( @@ -1096,7 +1052,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,11 +1077,7 @@ struct SPIDomain { { spi_instance.operation_flag = operation_flag; if (sizeof(T) % frame_size != 0) { - ErrorHandler( - "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) { - ErrorHandler( - "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) { - ErrorHandler( - "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) { - ErrorHandler( - "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) { - ErrorHandler( - "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( @@ -1255,7 +1191,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; } } @@ -1338,6 +1274,9 @@ struct SPIDomain { 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}; @@ -1346,7 +1285,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 +1293,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 +1301,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 +1309,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 +1317,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 +1325,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 +1412,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..de0ef1453 100644 --- a/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp +++ b/Inc/HALAL/Models/TimerDomain/TimerDomain.hpp @@ -381,7 +381,7 @@ struct TimerDomain { if (false) { } TimerXList else { - ErrorHandler("Invalid timer given to rcc_enable_timer"); + PANIC("Invalid timer given to rcc_enable_timer"); } #undef X } @@ -809,7 +809,7 @@ struct TimerDomain { inst->master = sMasterConfig; if (HAL_TIMEx_MasterConfigSynchronization(inst->hal_tim, &sMasterConfig) != HAL_OK) { - ErrorHandler("Unable to configure master synch"); + PANIC("Unable to configure master synch"); } } } @@ -877,7 +877,7 @@ struct TimerDomain { result *= 2; } } else { - ErrorHandler("Invalid timer ptr"); + PANIC("Invalid timer ptr"); } return result; @@ -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/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/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/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/Communication/FDCAN/FDCAN.hpp b/Inc/HALAL/Services/Communication/FDCAN/FDCAN.hpp index f6754716a..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)) { - ErrorHandler( - " 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/DFSDM/DFSDM.hpp b/Inc/HALAL/Services/DFSDM/DFSDM.hpp index c0f8c3bc1..0c9495780 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; } @@ -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 diff --git a/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp new file mode 100644 index 000000000..28d5c5dfe --- /dev/null +++ b/Inc/HALAL/Services/Diagnostics/Diagnostics.hpp @@ -0,0 +1,260 @@ +#pragma once + +#include "C++Utilities/CppUtils.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionTypes.hpp" + +namespace ST_LIB::TestAccess { +struct DiagnosticsHub; +} + +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 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 { + 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 active_time_s{0.0f}; +}; + +union DiagnosticPayload { + RuntimeDiagnosticPayload runtime; + ProtectionDiagnosticPayload protection; + + constexpr DiagnosticPayload() : runtime{} {} +}; + +struct DiagnosticRecord { + DiagnosticPriority priority{DiagnosticPriority::NORMAL}; + Severity severity{Severity::INFO}; + 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; + 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(); +}; + +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 + 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); + } + 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; + if (had_no_sinks) { + replay_history_to_pending(); + } + return sink; + } + } + + static void publish(DiagnosticRecord record); + 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, + 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_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, + Protections::RuleEdge edge, + const Protections::RuleSnapshot& snapshot + ); + static void flush(); + static void flush_urgent(); + +private: + friend struct ST_LIB::TestAccess::DiagnosticsHub; + + 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 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); + + 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(); + +private: + friend struct ST_LIB::TestAccess::DiagnosticsHub; + + static bool defaults_installed; +}; + +} // namespace Diagnostics 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/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 ad00091fe..6ee33ae45 100644 --- a/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp +++ b/Inc/HALAL/Services/InfoWarning/InfoWarning.hpp @@ -1,59 +1,28 @@ -/* - * InfoWarning.hpp - * - * Created on: Jun 12, 2024 - * Author: gonzalo - */ - #pragma once -#include "C++Utilities/CppUtils.hpp" +#include -class InfoWarning { -private: - static string description; - static string line; - static string func; - static string file; +#include "C++Utilities/CppUtils.hpp" +class RuntimeDiagnosticReporter { 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. - * - * @param format String which will be formated. - * @param args Arguments specifying data to print - */ - static void InfoWarningTrigger(string 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() - ); - - /** - * @brief Transmit the warning message. - */ - static void InfoWarningUpdate(); - - friend class BoundaryInterface; + 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 { \ - 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) 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/DualPWM.hpp b/Inc/HALAL/Services/PWM/DualPWM.hpp index b0121ec3c..b90c88c0e 100644 --- a/Inc/HALAL/Services/PWM/DualPWM.hpp +++ b/Inc/HALAL/Services/PWM/DualPWM.hpp @@ -74,7 +74,7 @@ class DualPWM { &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; @@ -108,7 +108,7 @@ class DualPWM { &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/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/PWM.hpp b/Inc/HALAL/Services/PWM/PWM.hpp index 305e4251c..56d82ca41 100644 --- a/Inc/HALAL/Services/PWM/PWM.hpp +++ b/Inc/HALAL/Services/PWM/PWM.hpp @@ -56,7 +56,7 @@ template class PWM { &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/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/HALAL/Services/Time/RTC.hpp b/Inc/HALAL/Services/Time/RTC.hpp index baf02c639..c000ba32c 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; @@ -21,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(); @@ -35,5 +34,3 @@ class Global_RTC { uint16_t year ); }; - -#endif 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.hpp b/Inc/ST-LIB.hpp index 8b6902f2b..0a43afce1 100644 --- a/Inc/ST-LIB.hpp +++ b/Inc/ST-LIB.hpp @@ -1,33 +1,36 @@ #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 +namespace ST_LIB { +extern void compile_error(const char* msg); + +template struct FaultPolicy { + static_assert( + IsStateMachineClass>, + "FaultPolicy operational machine must be a StateMachine" ); -#else - static void start(UART::Peripheral& printf_peripheral = UART::uart2); -#endif - static void update(); + static constexpr bool has_operational_machine = true; + static constexpr auto& operational_machine = OperationalMachine; + static constexpr Callback on_fault_enter = OnFaultEnter; }; -namespace ST_LIB { -extern void compile_error(const char* msg); +template struct FaultPolicyNoMachine { + static constexpr bool has_operational_machine = false; + static constexpr Callback on_fault_enter = OnFaultEnter; +}; + +using DefaultFaultPolicy = FaultPolicyNoMachine<>; + +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 { @@ -138,9 +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 { +template struct Board { +public: + using ProtectionEngine = BuildUtils::ProtectionEngineForRequests; + static consteval auto build_ctx() { DomainsCtx ctx{}; (devs.inscribe(ctx), ...); @@ -262,6 +293,9 @@ 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(); @@ -306,7 +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/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_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/FaultController.hpp b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp new file mode 100644 index 000000000..bcf65adde --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/FaultController.hpp @@ -0,0 +1,186 @@ +#pragma once + +#include "C++Utilities/CppUtils.hpp" +#include "ST-LIB_HIGH/Protections/ProtectionTypes.hpp" +#include "StateMachine/StateMachine.hpp" + +namespace ST_LIB::TestAccess { +struct FaultController; +} + +namespace Protections { +template class ProtectionEngine; +} + +class PanicReporter; +class FaultReporter; + +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 }; + +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{}; +}; + +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 FaultCause protection( + const char* protection_name, + Protections::RuleEdge edge, + const Protections::RuleSnapshot& snapshot + ); +}; + +class FaultController { +public: + using state_id = uint8_t; + 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; + { Policy::on_fault_enter } -> std::convertible_to; + }, + "Fault policy must expose has_operational_machine and on_fault_enter" + ); + + runtime_started = false; + 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(); + static void check_transitions(); + static bool is_faulted(); + static const FaultCause* latched_fault_cause(); + +private: + friend class PanicReporter; + friend class FaultReporter; + template friend class Protections::ProtectionEngine; + friend struct ST_LIB::TestAccess::FaultController; + + 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 + ); + 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; + } + } + + 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" + ); + + constexpr auto runtime_prototype = build_runtime_machine(); + auto* machine = construct_at( + reinterpret_cast(runtime_storage.bytes.data()), + runtime_prototype + ); + + 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 request_fault(const FaultCause& cause); + static void on_fault_state_enter(); + + 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/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..17865f8c1 100644 --- a/Inc/ST-LIB_HIGH/Protections/Protection.hpp +++ b/Inc/ST-LIB_HIGH/Protections/Protection.hpp @@ -1,99 +1,544 @@ #pragma once +#include + #include "C++Utilities/CppUtils.hpp" -#include "ErrorHandler/ErrorHandler.hpp" -#include "Boundary.hpp" +#include "HALAL/Services/Time/Scheduler.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 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.active_time_s = active_time_s; + 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, + 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 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; + } + + 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 (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() && + 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; + } +}; + +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_us = static_cast( + std::llround(static_cast(config.time_window_s) * 1'000'000.0) + ); + if (configured_window_us == 0) { + configured_window_us = 1; + } + } + + RuleEvaluation evaluate(const T& sample) { + const RuleState previous_state = tracker.previous_state(); + const RuleState state = TimeAccumulationEvaluator::compute( + sample, + config, + 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 { + .state = state, + .edge = edge, + .snapshot = RuleSnapshotBuilder::single_threshold( + RuleKind::TIME_ACCUMULATION, + active_magnitude, + state, + edge, + previous_state, + config.fault_threshold, + config.warning_threshold, + config.time_window_s, + active_time_s + ), + }; + } + + TimeAccumulationRuleConfig config{}; + RuleStateTracker tracker{}; + 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 > +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); } - } - if (oks_count) { - for (uint8_t i = 0; i < oks_count; i++) { - oks_triggered.push_back(boundaries[triggered_oks_idx[i]]); + }, + rule + ); +} + +template class Protection { +public: + Protection( + const char* name, + SampleSource source, + const std::array, RuleCount>& definitions + ) + : name(name), + source(source), + definitions(definitions), + rules(make_rule_models(definitions, std::make_index_sequence{})) {} + + const char* get_name() const { return name; } + void initialize() {} + + ProtectionEvaluation evaluate() { + ProtectionEvaluation evaluation{}; + const T sample = source.read(); + + 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++] = { + rule_evaluation.state, + rule_evaluation.edge, + rule_evaluation.snapshot, + }; } - } - if (warning_count) { - for (uint8_t i = 0; i < warning_count; i++) { - warnings_triggered.push_back(boundaries[triggered_protecions_idx[i]]); + + 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; + } + + void clear_runtime_state() { + rules = make_rule_models(definitions, std::make_index_sequence{}); + last_fault_publish_tick = 0; } - friend class ProtectionManager; + + 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: + 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; + std::array, RuleCount> definitions{}; + std::array, RuleCount> rules{}; + 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..20ba92478 --- /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..1c3b6dce2 --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionEngine.hpp @@ -0,0 +1,258 @@ +#pragma once + +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" +#include "ST-LIB_HIGH/Protections/FaultController.hpp" +#include "ST-LIB_HIGH/Protections/Protection.hpp" + +namespace Protections { + +namespace detail { + +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}; + } +} + +template struct BakedRules { + using sample_type = T; + static constexpr std::size_t rule_count = N; + + 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 ((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)>{ + 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]{}; + + constexpr FixedString(const char (&str)[N]) { + for (std::size_t index = 0; index < N; ++index) { + value[index] = str[index]; + } + } + + constexpr const char* c_str() const { return value; } + constexpr std::size_t size() const { return N - 1; } +}; + +template FixedString(const char (&)[N]) -> FixedString; + +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; + + detail::BakedRules rules{}; + + template consteval void inscribe(Ctx&) const {} +}; + +template +consteval auto protection(RuleDefs... definitions) { + using SampleType = detail::sample_type_from_source_t; + return ProtectionSpec{ + detail::bake_rules(definitions...) + }; +} + +template struct IsProtectionSpec : std::false_type {}; + +template +struct IsProtectionSpec> : std::true_type {}; + +template +concept ProtectionSpecLike = IsProtectionSpec>::value; + +template class ProtectionEngine { +public: + static_assert( + detail::AreUnique...>::value, + "Duplicate protection declarations must use distinct names or sources" + ); + + static constexpr std::size_t protection_count = sizeof...(ProtectionSpecs); + + template struct StorageForSpec { + using spec_type = std::remove_cvref_t; + 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 + reset(); + initialize_impl(std::make_index_sequence{}); + } + + 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: + 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) { + (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 + static void publish_edge_events( + 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); + } + + 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::same_as< + std::remove_cvref_t, + std::remove_cvref_t(std::tie(ProtectionSpecs...)))>>) { + return Index; + } else { + return spec_index(); + } + } + + inline static Storage protections{make_protection()...}; +}; + +} // namespace Protections diff --git a/Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp b/Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp new file mode 100644 index 000000000..28e225ff2 --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionErrors.hpp @@ -0,0 +1,15 @@ +#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, +}; + +} // 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..35bf4a773 --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/ProtectionTypes.hpp @@ -0,0 +1,117 @@ +#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 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 active_time_s{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..622c60a79 --- /dev/null +++ b/Inc/ST-LIB_HIGH/Protections/Rules.hpp @@ -0,0 +1,248 @@ +#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}; +}; + +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) { + 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()); + } + + 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, + }}; +} + +} // 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) { + 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 + ); +} + +} // 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..737096384 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" @@ -26,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 297523087..3bac21830 100644 --- a/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp +++ b/Inc/ST-LIB_LOW/ErrorHandler/ErrorHandler.hpp @@ -1,65 +1,31 @@ -/* - * 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 string description; - static string line; - static string func; - static string file; - +class PanicReporter { 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. - * - * @param format String which will be formated. - * @param args Arguments specifying data to print - * @return uint8_t Id of the service. - */ - static void ErrorHandlerTrigger(string 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() - ); - - /** - * @brief Transmit the error message. - */ - static void ErrorHandlerUpdate(); + static void Trigger(const std::source_location& location, const char* format, ...); + static void Flush(); +}; - friend class BoundaryInterface; +class FaultReporter { +public: + static void Trigger(const std::source_location& location, const char* format, ...); + static void Flush(); }; -#define ErrorHandler(x, ...) \ +#define PANIC(x, ...) \ + do { \ + PanicReporter::Trigger(std::source_location::current(), x __VA_OPT__(, ) __VA_ARGS__); \ + } while (0) + +#define FAULT(x, ...) \ do { \ - ErrorHandlerModel::SetMetaData(__LINE__, __FUNCTION__, __FILE__); \ - ErrorHandlerModel::ErrorHandlerTrigger(x, ##__VA_ARGS__); \ + FaultReporter::Trigger(std::source_location::current(), x __VA_OPT__(, ) __VA_ARGS__); \ } while (0) 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/Sd/Sd.hpp b/Inc/ST-LIB_LOW/Sd/Sd.hpp index b2eb5f00d..6f38bc045 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,8 +508,8 @@ 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 " - "with current PLL1 Q clock"); + PANIC("SDMMC clock divider too high, cannot achieve target frequency " + "with current PLL1 Q clock"); } inst.hsd.Init.ClockDiv = translated_clock_div; @@ -531,15 +531,15 @@ 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 " - "higher divider?"); + PANIC("SDMMC clock configuration failed, maybe try with a slower clock or " + "higher divider?"); } // Ensure PLL1Q output is enabled __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/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/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 diff --git a/Src/HALAL/HALAL.cpp b/Src/HALAL/HALAL.cpp index bc38c0609..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) { - ErrorHandler("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/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..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) { - ErrorHandler( - "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/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/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/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/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/Diagnostics/DiagnosticFormatter.cpp b/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp new file mode 100644 index 000000000..f62d84385 --- /dev/null +++ b/Src/HALAL/Services/Diagnostics/DiagnosticFormatter.cpp @@ -0,0 +1,267 @@ +#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 | Active: %.3fs", + static_cast(protection.time_window_s), + static_cast(protection.active_time_s) + ); + } + 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_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: + 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..f87388a76 --- /dev/null +++ b/Src/HALAL/Services/Diagnostics/DiagnosticSinks.cpp @@ -0,0 +1,215 @@ +#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..6ffc9c1a8 --- /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::is_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..9bad45779 --- /dev/null +++ b/Src/HALAL/Services/Diagnostics/DiagnosticsHub.cpp @@ -0,0 +1,333 @@ +#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_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) { + 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) { + 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; + pending_records[pending_count].delivered_mask = 0; + 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; + } + + for (size_t next = index + 1; next < pending_count; ++next) { + pending_records[next - 1] = pending_records[next]; + } + 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); + + if (sink_count == 0) { + return; + } + + push_pending(record); +} + +DiagnosticRecord RecordFactory::runtime_fault( + const char* message, + bool truncated, + const RuntimeSourceMetadata& metadata, + DiagnosticPriority priority +) { + DiagnosticRecord record{}; + record.priority = priority; + record.severity = Severity::FAULT; + record.category = Category::RUNTIME_FAULT; + record.timestamp = DiagnosticTimestampProvider::capture(); + 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, metadata.function_name); + copy_c_string(record.payload.runtime.file_name, metadata.file_name); + return record; +} + +DiagnosticRecord RecordFactory::runtime_panic( + const char* message, + bool truncated, + 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(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_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, + DiagnosticPriority priority +) { + DiagnosticRecord record{}; + record.priority = priority; + 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.active_time_s = snapshot.active_time_s; + return record; +} + +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); + 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::flush_urgent() { flush_pending(true); } + +void Hub::flush() { + flush_urgent(); + flush_pending(false); +} + +} // namespace Diagnostics 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..3940d1ea4 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,9 +93,7 @@ void MultiplierAccelerator::software_preload( #if FMAC_ERROR_CHECK != 0 if (amount_to_preload > MemoryLayout.FInSize || amount_to_preload > MemoryLayout.FeedbackInSize) { - ErrorHandler( - "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) { @@ -109,7 +107,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 +116,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 +124,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 +138,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 +159,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 +191,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/InfoWarning/InfoWarning.cpp b/Src/HALAL/Services/InfoWarning/InfoWarning.cpp index ad7da2caf..711bc7f17 100644 --- a/Src/HALAL/Services/InfoWarning/InfoWarning.cpp +++ b/Src/HALAL/Services/InfoWarning/InfoWarning.cpp @@ -1,303 +1,64 @@ -/* - * InfoWarning.cpp - * - * Created on: Jun 12, 2024 - * Author: gonzalo - */ - #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 { - -#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; - } +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" - printf("Warning: %s%s", description.c_str(), endl); - warning_sent_via_uart = true; - return true; -#else - (void)description; - return true; -#endif -} +namespace { -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( +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); + + switch (severity) { + case Diagnostics::Severity::WARNING: + Diagnostics::Hub::publish_runtime_warning( 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) + written < 0 || static_cast(written) >= sizeof(buffer), + static_cast(location.line()), + location.function_name(), + location.file_name() ); - 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( + case Diagnostics::Severity::INFO: + Diagnostics::Hub::publish_runtime_info( buffer, - sizeof(buffer), - " | Uptime: %02u:%02u:%02u.%06u", - hours, - minutes, - seconds, - micros + written < 0 || static_cast(written) >= sizeof(buffer), + static_cast(location.line()), + location.function_name(), + location.file_name() ); + return; + case Diagnostics::Severity::FAULT: + std::unreachable(); } - 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; - -void InfoWarning::SetMetaData(int line, const char* func, const char* file) { - InfoWarning::line = to_string(line); - InfoWarning::func = string(func); - InfoWarning::file = string(file); +void RuntimeDiagnosticReporter::TriggerWarning( + const std::source_location& location, + const char* format, + ... +) { + va_list arguments; + va_start(arguments, format); + publish_runtime_diagnostic(Diagnostics::Severity::WARNING, location, format, arguments); + va_end(arguments); } -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 RuntimeDiagnosticReporter::TriggerInfo( + const std::source_location& location, + const char* format, + ... +) { 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); + publish_runtime_diagnostic(Diagnostics::Severity::INFO, location, 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(); } -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 RuntimeDiagnosticReporter::Flush() { Diagnostics::Hub::flush(); } 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/HALAL/Services/Time/RTC.cpp b/Src/HALAL/Services/Time/RTC.cpp index 6fbe0edb8..602551f7e 100644 --- a/Src/HALAL/Services/Time/RTC.cpp +++ b/Src/HALAL/Services/Time/RTC.cpp @@ -1,12 +1,20 @@ #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; 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() { @@ -20,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; @@ -29,7 +37,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; @@ -40,7 +48,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; } @@ -51,7 +59,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; } @@ -67,6 +75,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() { @@ -113,17 +123,18 @@ 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) { global_RTC = get_rtc_timestamp(); } } + void Global_RTC::update_rtc_data() { if (!ensure_started()) { return; @@ -134,3 +145,39 @@ void Global_RTC::update_rtc_data() { } global_RTC = get_rtc_timestamp(); } + +#else + +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; } + +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 diff --git a/Src/HALAL/Services/Time/Scheduler.cpp b/Src/HALAL/Services/Time/Scheduler.cpp index ac215b4b4..dd76b155f 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 = @@ -99,9 +102,12 @@ void Scheduler::update() { uint64_t Scheduler::get_global_tick() { SchedLock(); - uint64_t val = global_tick_us_ + Scheduler_global_timer->CNT; + uint64_t tick = global_tick_us_; + if (Scheduler_global_timer != nullptr) { + tick += Scheduler_global_timer->CNT; + } SchedUnlock(); - return val; + return tick; } inline uint8_t Scheduler::allocate_slot() { diff --git a/Src/ST-LIB.cpp b/Src/ST-LIB.cpp deleted file mode 100644 index fb8a4378d..000000000 --- a/Src/ST-LIB.cpp +++ /dev/null @@ -1,52 +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); - STLIB_LOW::start(); - STLIB_HIGH::start(); -} - -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); - STLIB_LOW::start(); - STLIB_HIGH::start(); -} - -#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 - ErrorHandlerModel::ErrorHandlerUpdate(); - MDMA::update(); -} 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/FaultController.cpp b/Src/ST-LIB_HIGH/Protections/FaultController.cpp new file mode 100644 index 000000000..1d908c0f9 --- /dev/null +++ b/Src/ST-LIB_HIGH/Protections/FaultController.cpp @@ -0,0 +1,212 @@ +#include "ST-LIB_HIGH/Protections/FaultController.hpp" + +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" + +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: + 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; +} + +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; + } + + 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(); +} + +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); +} + +bool FaultController::is_faulted() { return faulted; } + +const FaultCause* FaultController::latched_fault_cause() { + return has_latched_cause ? &latched_cause : nullptr; +} + +void FaultController::on_fault_state_enter() { + if (on_fault_enter != nullptr) { + on_fault_enter(); + } +} 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 deleted file mode 100644 index d67acbb21..000000000 --- a/Src/ST-LIB_HIGH/ST-LIB_HIGH.cpp +++ /dev/null @@ -1,12 +0,0 @@ -/* - * ST-LIB_LOW.cpp - * - * Created on: 19 jun. 2023 - * Author: ricardo - */ - -#include "ST-LIB_HIGH.hpp" - -void STLIB_HIGH::start() { - // ProtectionManager::add_standard_protections(); -} 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/ErrorHandler/ErrorHandler.cpp b/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp index b0c4897da..286b09f7f 100644 --- a/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp +++ b/Src/ST-LIB_LOW/ErrorHandler/ErrorHandler.cpp @@ -1,305 +1,53 @@ -/* - * ErrorHandler.cpp - * - * Created on: Dec 22, 2022 - * Author: Pablo - */ - #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 { - -#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; - } +#include "HALAL/Services/Diagnostics/Diagnostics.hpp" +#include "ST-LIB_HIGH/Protections/FaultController.hpp" - 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; - } +namespace { - static constexpr uint16_t id = ERROR_HANDLER_TCP_ORDER_ID; - vector buffer{}; +struct RuntimeFatalMessage { + char buffer[Diagnostics::Config::runtime_message_capacity + 1]{}; + bool truncated{false}; }; -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 +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 -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; +void FaultReporter::Trigger(const std::source_location& location, const char* format, ...) { + va_list arguments; + va_start(arguments, format); + const RuntimeFatalMessage message = format_runtime_fatal_message(format, arguments); + va_end(arguments); -void ErrorHandlerModel::SetMetaData(int line, const char* func, const char* file) { - ErrorHandlerModel::line = to_string(line); - ErrorHandlerModel::func = string(func); - ErrorHandlerModel::file = string(file); + FaultController::request_fault(FaultCause::runtime_fault( + message.buffer, + message.truncated, + static_cast(location.line()), + location.function_name(), + location.file_name() + )); } -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 PanicReporter::Trigger(const std::source_location& location, const char* format, ...) { 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 RuntimeFatalMessage message = format_runtime_fatal_message(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(); + FaultController::request_fault(FaultCause::panic( + message.buffer, + message.truncated, + static_cast(location.line()), + location.function_name(), + location.file_name() + )); } -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 PanicReporter::Flush() { Diagnostics::Hub::flush(); } +void FaultReporter::Flush() { Diagnostics::Hub::flush(); } 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/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"); } } 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); - } -} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index da790098b..f2e81e4d4 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -17,15 +17,24 @@ 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}/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 @@ -55,6 +64,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..8a31a15e2 --- /dev/null +++ b/Tests/TestAccess.hpp @@ -0,0 +1,42 @@ +#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 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); + } +}; + +} // namespace ST_LIB::TestAccess diff --git a/Tests/Time/common_tests.cpp b/Tests/Time/common_tests.cpp index eca45e017..0c741093c 100644 --- a/Tests/Time/common_tests.cpp +++ b/Tests/Time/common_tests.cpp @@ -1,12 +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" -std::string ErrorHandlerModel::line; -std::string ErrorHandlerModel::func; -std::string ErrorHandlerModel::file; - -namespace ST_LIB::TestErrorHandler { +namespace ST_LIB::TestPanicReporter { bool fail_on_error = true; int call_count = 0; @@ -16,50 +17,44 @@ void reset() { } 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); -} - -void ErrorHandlerModel::ErrorHandlerTrigger(string format, ...) { - (void)format; - ST_LIB::TestErrorHandler::call_count++; - if (ST_LIB::TestErrorHandler::fail_on_error) { +} // namespace ST_LIB::TestPanicReporter + +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::TestPanicReporter::call_count++; + FaultController::request_fault(FaultCause::panic( + buffer, + written < 0 || static_cast(written) >= sizeof(buffer), + 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 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); + + 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 set_fail_on_error(bool enabled) { fail_on_error = enabled; } -}; // namespace ST_LIB::TestInfoWarning - -void InfoWarning::SetMetaData(int line, const char* func, const char* file) { - InfoWarning::line = to_string(line); - InfoWarning::func = string(func); - InfoWarning::file = string(file); -} - -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/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/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) { diff --git a/Tests/diagnostics_test.cpp b/Tests/diagnostics_test.cpp new file mode 100644 index 000000000..be02475d6 --- /dev/null +++ b/Tests/diagnostics_test.cpp @@ -0,0 +1,490 @@ +#include + +#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" +#include "ST-LIB_HIGH/Protections/SampleSource.hpp" +#include "StateMachine/StateMachine.hpp" +#include "TestAccess.hpp" + +namespace ST_LIB::TestPanicReporter { +void set_fail_on_error(bool enabled); +void reset(); +} // namespace ST_LIB::TestPanicReporter + +namespace { + +namespace TestAccess = ST_LIB::TestAccess; +namespace TestPanicReporter = ST_LIB::TestPanicReporter; + +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{}; +}; + +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(); +} + +void on_fault_enter() { fault_enter_calls++; } + +inline float monitored_value = 2.0f; +inline constexpr auto monitored_protection = Protections::protection<"monitored_value", monitored_value>( + Protections::Rules::below(1.0f, 1.5f) +); +using MonitoredProtectionEngine = Protections::ProtectionEngine; + +inline float time_value = 0.0f; +inline constexpr auto time_protection = Protections::protection<"time_value", time_value>( + Protections::Rules::time_accumulation(10.0f, 0.001f) +); +using TimeProtectionEngine = Protections::ProtectionEngine; + +inline float time_reset_value = 0.0f; +inline constexpr auto time_reset_protection = Protections::protection<"time_reset_value", time_reset_value>( + Protections::Rules::time_accumulation(10.0f, 0.001f) +); +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"); +} + +uint32_t emit_warning_and_return_line() { + constexpr uint32_t expected_line = __LINE__ + 1; + WARNING("source location warning"); + return expected_line; +} + +struct NoMachinePolicy { + static constexpr bool has_operational_machine = false; + static constexpr Callback on_fault_enter = &::on_fault_enter; +}; + +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 { + TestAccess::DiagnosticsHub::clear(); + MonitoredProtectionEngine::reset(); + TimeProtectionEngine::reset(); + TimeResetProtectionEngine::reset(); + TestAccess::FaultController::clear(); + reset_operational_machine(); + TestPanicReporter::reset(); + fault_enter_calls = 0; + Scheduler::global_tick_us_ = 0; + Scheduler_global_timer = nullptr; + + FaultController::install_runtime(); + FaultController::start(); + } +}; + +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(TestAccess::DiagnosticsHub::history_size(), 1u); + 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); + 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(TestAccess::DiagnosticsHub::pending_size(), 1u); + + Diagnostics::Hub::flush(); + EXPECT_EQ(stable_sink->publish_calls, 1u); + EXPECT_EQ(flaky_sink->publish_calls, 2u); + EXPECT_EQ(TestAccess::DiagnosticsHub::pending_size(), 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(TestAccess::DiagnosticsHub::pending_size(), Diagnostics::Config::pending_capacity); +} + +TEST_F(DiagnosticsHubTest, ReinstallingRuntimeClearsLatchedFaultState) { + TestAccess::FaultController::request_fault(make_test_runtime_fault("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) { + 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_EQ(FaultController::latched_fault_cause()->kind, FaultCauseKind::RUNTIME_FAULT); + EXPECT_STREQ(FaultController::latched_fault_cause()->runtime.message, "fault once"); +} + +TEST_F(DiagnosticsHubTest, FaultControllerDelegatesToOperationalMachineWhileOperational) { + FaultController::install_runtime(); + reset_operational_machine(); + FaultController::start(); + + transition_to_hold = true; + FaultController::check_transitions(); + + 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(); + + TestAccess::FaultController::request_fault(make_test_runtime_fault("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(); + + TestAccess::FaultController::request_fault(make_test_runtime_fault("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(DiagnosticsBootstrapTest, PanicBeforeRuntimeInstallationSurvivesBootstrapAndIsDelivered) { + TestAccess::DiagnosticsHub::clear(); + MonitoredProtectionEngine::reset(); + TimeProtectionEngine::reset(); + TimeResetProtectionEngine::reset(); + 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) { + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + + MonitoredProtectionEngine::initialize(); + monitored_value = 0.5f; + MonitoredProtectionEngine::evaluate(); + Diagnostics::Hub::flush(); + + ASSERT_FALSE(sink->records.empty()); + 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); + EXPECT_EQ(sink->records.front().payload.protection.rule_kind, Protections::RuleKind::BELOW); +} + +TEST_F(DiagnosticsHubTest, TimeAccumulationUsesSchedulerTickForContinuousDuration) { + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + + TimeProtectionEngine::initialize(); + + time_value = 12.0f; + Scheduler::global_tick_us_ = 0; + TimeProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); + + Scheduler::global_tick_us_ = 500; + TimeProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); + + Scheduler::global_tick_us_ = 1'000; + TimeProtectionEngine::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) { + TimeResetProtectionEngine::initialize(); + + time_reset_value = 12.0f; + Scheduler::global_tick_us_ = 0; + TimeResetProtectionEngine::evaluate(); + + Scheduler::global_tick_us_ = 700; + TimeResetProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); + + time_reset_value = 0.0f; + Scheduler::global_tick_us_ = 800; + TimeResetProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); + + Scheduler::global_tick_us_ = 1'600; + TimeResetProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); + + time_reset_value = 12.0f; + Scheduler::global_tick_us_ = 1'600; + TimeResetProtectionEngine::evaluate(); + + Scheduler::global_tick_us_ = 2'300; + TimeResetProtectionEngine::evaluate(); + EXPECT_FALSE(FaultController::is_faulted()); +} + +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; + + FAULT("runtime fault %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_FAULT); + EXPECT_EQ(sink->records.front().severity, Diagnostics::Severity::FAULT); + EXPECT_EQ(sink->records.front().priority, Diagnostics::DiagnosticPriority::URGENT); +} + +TEST_F(DiagnosticsHubTest, WarningDoesNotEnterFault) { + auto sink_result = Diagnostics::Hub::emplace_sink(); + ASSERT_TRUE(sink_result.has_value()); + auto* sink = *sink_result; + + WARNING("runtime warning %d", 3); + Diagnostics::Hub::flush(); + + ASSERT_FALSE(sink->records.empty()); + 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) { + 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/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()); } diff --git a/docs/protections-and-diagnostics.md b/docs/protections-and-diagnostics.md new file mode 100644 index 000000000..a7601e0cc --- /dev/null +++ b/docs/protections-and-diagnostics.md @@ -0,0 +1,523 @@ +# 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()` +- `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 +polls: + +- `FaultController::check_transitions()` + +The global fault model is always the same: + +- the framework owns a global runtime with two states: `OPERATIONAL` and `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` + +```mermaid +flowchart TD + 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["Board::evaluate_protections()"] + 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 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 Declaring Protections + +Protections are compile-time board requests. A protection request: + +- 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 + +Rules are created through the factories in `Protections::Rules` and passed to +`Protections::protection<"name", source>(...)`. + +Available rule factories: + +- `Rules::below(...)` +- `Rules::above(...)` +- `Rules::range(...)` +- `Rules::equals(...)` +- `Rules::not_equals(...)` +- `Rules::time_accumulation(...)` + +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: + +```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 Protection Lifecycle + +Declare protections at namespace scope and pass them to `Board`. + +The intended lifecycle is: + +1. compile-time declaration +2. `Board::init()` +3. evaluation and flushing in the runtime loop + +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 + +```cpp +#include "ST-LIB.hpp" + +using namespace ST_LIB; + +constexpr auto led = DigitalOutputDomain::DigitalOutput(PF13); + +float bus_voltage = 0.0f; + +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) +); + +using MainBoard = Board; + +int main() { + MainBoard::init(); + + while (1) { + MainBoard::evaluate_protections(); + 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. + +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`. + +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. +} + +using MainBoard = Board, led>; + +int main() { + MainBoard::init(); + + while (1) { + FaultController::check_transitions(); + MainBoard::evaluate_protections(); + 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`, user code should use `PANIC(...)` or + `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 + +`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: + +- `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 Internal Fault Primitive + +Internally, protections and fatal runtime reporters converge on: + +```cpp +FaultController::request_fault(cause); +``` + +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`. + +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. + +### 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 + +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: + +- `Protections::protection<"name", source>(...)` +- `Board::ProtectionEngine` +- `Board::evaluate_protections()` +- `Protections::Rules::*` + +Internal model: + +- 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` + +Supported rule kinds: + +- `BELOW` +- `ABOVE` +- `RANGE` +- `EQUALS` +- `NOT_EQUALS` +- `TIME_ACCUMULATION` + +`TIME_ACCUMULATION` uses `Scheduler::get_global_tick()` to measure real elapsed time. +It no longer assumes a fixed evaluation rate. + +`Board::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, as early as possible in the bootstrap path: + +- default diagnostic sinks +- the global fault runtime + +before clock/peripheral setup and 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` + +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 + +`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 rule 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 diff --git a/docs/st-lib-board-contract.md b/docs/st-lib-board-contract.md index 404d99871..0575a8a95 100644 --- a/docs/st-lib-board-contract.md +++ b/docs/st-lib-board-contract.md @@ -7,13 +7,30 @@ 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`. + +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. + +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,10 +52,10 @@ 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` +- `template consteval ... inscribe(Ctx&) const` or any compatible return type if it naturally inscribes multiple dependent entries. @@ -49,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. @@ -68,6 +89,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()`: @@ -77,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 @@ -94,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 @@ -190,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?