From 7b986a20ae9a8c63dcf5dd150d990824abfaa1f0 Mon Sep 17 00:00:00 2001 From: Hibyehello <36666883+Hibyehello@users.noreply.github.com> Date: Tue, 31 Mar 2026 18:53:00 -0600 Subject: [PATCH 01/13] Initial Service, Dispatcher, and Queue class --- lib/can/dispatcher.h | 31 +++++++++++++++++++++++++++++++ lib/can/service.h | 23 +++++++++++++++++++++-- lib/core/queue/queue.h | 11 +++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 lib/can/dispatcher.h create mode 100644 lib/core/queue/queue.h diff --git a/lib/can/dispatcher.h b/lib/can/dispatcher.h new file mode 100644 index 0000000..fbc6971 --- /dev/null +++ b/lib/can/dispatcher.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "../core/queue/queue.h" +#include "types.h" + +class watchdog; + +namespace CAN { + +class Dispatcher { +public: + const void send(const Frame* data); + const void digest_read(void *data); + // This means that the dispatcher owns the watchdogs, not sure if that is what we want + void register_route(std::unique_ptr watchdog); + void unregister_route(); + + const dispatcher* getInstance(); + dispatcher(const dispatcher&) = delete; + dispatcher& operator=(const dispatcher&) = delete; +private: + dispatcher(); + Core::queue queue_rx; + Core::queue queue_tx; + std::map> node_routes; +}; + +} // namespace CAN \ No newline at end of file diff --git a/lib/can/service.h b/lib/can/service.h index 6d6fe48..ad4139d 100644 --- a/lib/can/service.h +++ b/lib/can/service.h @@ -3,11 +3,15 @@ #define CAN_SERVICE_H #include +#include #include "types.h" +#include "dispatcher.h" + +// TODO: Handle Errors namespace CAN { -class Service { +class Old_Service { public: virtual const Result install_driver(const GeneralConfig *g_config, const TimingConfig *t_config, const FilterConfig *f_config) = 0; virtual const Result uninstall_driver() = 0; @@ -23,7 +27,22 @@ class Service { virtual const Result clear_receive_queue() = 0; virtual const Result reset_pin(const PIN pin) = 0; - virtual ~Service() = default; + virtual ~Old_Service() = default; +}; + +class Service { +public: + virtual void setup() = 0; + virtual void stop_listening() = 0; + virtual void start_listening() = 0; + virtual void digest_read() = 0; // Grabs from dispatcher + virtual void raw_send(void *data) = 0; + virtual void raw_read(void *data) = 0; + virtual void recover() = 0; + + ~Service() = default; +private: + std::shared_ptr m_dispatcher; // dispatcher is a singleton, so Service doesn't own it }; diff --git a/lib/core/queue/queue.h b/lib/core/queue/queue.h new file mode 100644 index 0000000..a54423d --- /dev/null +++ b/lib/core/queue/queue.h @@ -0,0 +1,11 @@ +#pragma once + +namespace Core { + +class queue { +public: + virtual void insert(void* data); + virtual void remove(void* data); +}; + +} // namespace Core \ No newline at end of file From 745b03ef6f17c4c579c0b7c60c64bafae6d3286c Mon Sep 17 00:00:00 2001 From: Porter McGary <56183563+mcgaryp@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:02:32 -0600 Subject: [PATCH 02/13] CAN for ESP32s --- lib/can/TWAI/twai_service.cpp | 174 ++++++++++++++++++ lib/can/TWAI/twai_service.h | 33 ++++ lib/can/can.h | 3 +- lib/can/dispatcher.cpp | 37 ++++ lib/can/dispatcher.h | 26 +-- lib/can/esp32_s3_can_service.h | 81 -------- lib/can/provider.cpp | 110 ----------- lib/can/provider.h | 102 ---------- lib/can/service.h | 35 +--- lib/can/transmitter.cpp | 47 +++++ lib/can/transmitter.h | 31 ++++ lib/can/types.h | 162 +--------------- lib/core/core.h | 2 + lib/core/logger.h | 8 + lib/core/logger/i_logger.h | 29 +++ lib/core/logger/logger.h | 77 ++++++++ lib/core/logger/types.h | 26 +++ lib/core/queue.h | 6 + lib/core/queue/i_queue.h | 34 ++++ lib/core/queue/queue.h | 11 -- test/mocks/can/mock_can_service.cpp | 33 ++++ test/mocks/can/mock_can_service.h | 143 ++------------ test/mocks/logger/serial_logger.h | 33 ++++ .../{test_codables.cpp => test_frame.cpp} | 14 +- test/test_can/test_main.cpp | 3 +- test/test_can/test_main.h | 3 +- test/test_can/test_provider.cpp | 79 -------- 27 files changed, 620 insertions(+), 722 deletions(-) create mode 100644 lib/can/TWAI/twai_service.cpp create mode 100644 lib/can/TWAI/twai_service.h create mode 100644 lib/can/dispatcher.cpp delete mode 100644 lib/can/esp32_s3_can_service.h delete mode 100644 lib/can/provider.cpp delete mode 100644 lib/can/provider.h create mode 100644 lib/can/transmitter.cpp create mode 100644 lib/can/transmitter.h create mode 100644 lib/core/logger.h create mode 100644 lib/core/logger/i_logger.h create mode 100644 lib/core/logger/logger.h create mode 100644 lib/core/logger/types.h create mode 100644 lib/core/queue.h create mode 100644 lib/core/queue/i_queue.h delete mode 100644 lib/core/queue/queue.h create mode 100644 test/mocks/can/mock_can_service.cpp create mode 100644 test/mocks/logger/serial_logger.h rename test/test_can/{test_codables.cpp => test_frame.cpp} (89%) delete mode 100644 test/test_can/test_provider.cpp diff --git a/lib/can/TWAI/twai_service.cpp b/lib/can/TWAI/twai_service.cpp new file mode 100644 index 0000000..d71b4e1 --- /dev/null +++ b/lib/can/TWAI/twai_service.cpp @@ -0,0 +1,174 @@ +#if defined(ESP32) + +#include "twai_service.h" + +using namespace CORE; + +// This is a link to the documentation for the ESP32 TWAI driver, which is what this service is built on top of. +// https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/api-reference/peripherals/twai.html + +bool TWAIService::setup(const void * config) { + if (!config) return false; + + // 1. Validate Config Type + const BaseConfig* base = static_cast(config); + if (base->type != ServiceConfigType::TWAI) return false; + + // 2. Ensure a clean state + // We don't care if tear_down "fails," we just want the driver uninstalled. + tear_down(); + + const TWAIConfig* twai_config = static_cast(config); + + // 3. Install Driver + // This allocates the RX/TX Ring Buffers in internal RAM. + esp_err_t install_res = twai_driver_install( + twai_config->general_config, + twai_config->timing_config, + twai_config->filter_config + ); + + if (install_res != ESP_OK) { + // Log: install_res (e.g., out of memory or invalid pins) + LOG_ERR("TWAIService", "Failed to install TWAI driver: %d", install_res); + return false; + } + + // 4. Start the Driver + // This transitions the hardware from 'Stopped' to 'Running' (Alert/Listen mode) + esp_err_t start_res = twai_start(); + if (start_res != ESP_OK) { + LOG_ERR("TWAIService", "Failed to start TWAI driver: %d", start_res); + esp_err_t uninstall_res = twai_driver_uninstall(); // Cleanup if start fails + if (uninstall_res != ESP_OK) { + LOG_ERR("TWAIService", "Failed to uninstall TWAI driver after failed start: %d", uninstall_res); + } + return false; + } + + return true; +} + +bool TWAIService::tear_down() { + twai_status_info_t status; + // If the driver is not installed, twai_get_status_info returns ESP_ERR_INVALID_STATE + if (get_status(status) == ESP_ERR_INVALID_STATE) { + return true; + } + + // 1. If we are running or recovering, we must STOP first + if (status.state == TWAI_STATE_RUNNING || status.state == TWAI_STATE_RECOVERING) { + esp_err_t stop_res = twai_stop(); + if (stop_res != ESP_OK) { + // Log: Failed to stop, but we will try to uninstall anyway + LOG_ERR("TWAIService", "Failed to stop TWAI driver during tear_down: %d", stop_res); + } + } + + // 2. Uninstall the driver to free up the RX/TX ring buffers (RAM) + esp_err_t uninstall_res = twai_driver_uninstall(); + if (uninstall_res != ESP_OK) { + // Log: Fatal error during uninstall + LOG_ERR("TWAIService", "Failed to uninstall TWAI driver: %d", uninstall_res); + return false; + } + + return true; +} + +bool TWAIService::send(Frame& frame) { + twai_status_info_t status; + if (get_status(status) != ESP_OK || status.state != TWAI_STATE_RUNNING) { + return false; + } + + twai_message_t twai_frame; + twai_frame.identifier = frame.identifier; + twai_frame.data_length_code = frame.data_length_code; + for (int i = 0; i < frame.data_length_code; i++) { + twai_frame.data[i] = frame.data[i]; + } + + esp_err_t result = twai_transmit(twai_frame, portMAX_DELAY); + if (result != ESP_OK) { + LOG_ERR("TWAIService", "Failed to transmit TWAI frame: %d", result); + return false; + } + + return true; +} + +const Frame* TWAIService::read() { + twai_status_info_t status; + if (get_status(status) != ESP_OK || status.state != TWAI_STATE_RUNNING) { + return nullptr; + } + + twai_message_t twai_msg; + // Use a small timeout or portMAX_DELAY depending on your Dispatcher task strategy + esp_err_t result = twai_receive(&twai_msg, 100 / portTICK_PERIOD_MS); + if (result != ESP_OK) { + if (result != ESP_ERR_TIMEOUT) { + LOG_ERR("TWAIService", "Failed to receive TWAI frame: %d", result); + } + return nullptr; + } + + // IMPORTANT: Avoid 'Frame* frame = {};' as that is a null pointer. + // Use a static or member variable to return the address, or better yet, + // change the interface to 'bool read(Frame& outFrame)'. + static Frame frame_buffer; + frame_buffer.identifier = twai_msg.identifier; + frame_buffer.data_length_code = twai_msg.data_length_code; + memcpy(frame_buffer.data, twai_msg.data, twai_msg.data_length_code); + + return &frame_buffer; +} + +bool TWAIService::recover() { + twai_status_info_t status; + if (get_status(status) != ESP_OK) { + return false; + } + + switch (status.state) { + case TWAI_STATE_BUS_OFF: + // The hardware is 'dead'. Start the 128-pulse countdown. + if (twai_initiate_recovery() == ESP_OK) { + LOG_WARN("TWAIService", "Bus off detected. Initiating recovery..."); + return true; + } + break; + + case TWAI_STATE_STOPPED: + // Recovery finished! But we are still 'Stopped'. + // We need to re-start to get back to 'Running'. + if (twai_start() == ESP_OK) { + LOG_INFO("TWAIService", "Recovery complete. Bus is back online."); + return true; + } + break; + + case TWAI_STATE_RECOVERING: + // We are currently waiting for those 128 idle sequences. + // Just wait; don't trigger anything else. + break; + + case TWAI_STATE_RUNNING: + // Everything is fine. + break; + } + + return true; +} + +esp_err_t TWAIService::get_status(twai_status_info_t& status) { + esp_err_t res = twai_get_status_info(&status); + if (res != ESP_OK) { + // Log: Failed to get status info + LOG_ERR("TWAIService", "Failed to get TWAI status info: %d", res); + } + return res; +} + +#endif \ No newline at end of file diff --git a/lib/can/TWAI/twai_service.h b/lib/can/TWAI/twai_service.h new file mode 100644 index 0000000..ce0513c --- /dev/null +++ b/lib/can/TWAI/twai_service.h @@ -0,0 +1,33 @@ +#ifndef TWAI_SERVICE_H +#define TWAI_SERVICE_H + +#if defined(ESP32) + +#include +#include +#include + +#include "service.h" + +using namespace CAN; + +struct TWAIConfig: public BaseServiceConfig { + const twai_general_config_t *general_config; + const twai_timing_config_t *timing_config; + const twai_filter_config_t *filter_config; +} + +class TWAIService : public Service { +public: + bool setup(const void * config) override; + bool tear_down() override; + bool send(Frame& frame) override; + const Frame* read() override; + bool recover() override; + + ~TWAIService() = default; +}; + +#endif // ESP32 + +#endif // TWAI_SERVICE_H \ No newline at end of file diff --git a/lib/can/can.h b/lib/can/can.h index 88cc129..06ecc28 100644 --- a/lib/can/can.h +++ b/lib/can/can.h @@ -2,8 +2,9 @@ #ifndef CAN_H #define CAN_H -#include "provider.h" +#include "dispatcher.h" #include "service.h" +#include "transmitter.h" #include "types.h" #endif // CAN_H \ No newline at end of file diff --git a/lib/can/dispatcher.cpp b/lib/can/dispatcher.cpp new file mode 100644 index 0000000..8e7632a --- /dev/null +++ b/lib/can/dispatcher.cpp @@ -0,0 +1,37 @@ +#include "dispatcher.h" + +namespace CAN { + +Dispatcher& Dispatcher::get_instance() { + static Dispatcher instance; + return instance; +} + +void Dispatcher::enqueue(const Frame& data) { + if (queue_rx == nullptr) { + // Queue not set, cannot enqueue + LOG_ERR("Dispatcher", "Queue not set, cannot enqueue frame with ID: %u", data.identifier); + return; + } + + queue_rx->enqueue(data); +} + +void Dispatcher::dispatch() { + if (queue_rx == nullptr) { + // Queue not set, cannot dispatch + LOG_ERR("Dispatcher", "Queue not set, cannot dispatch frames"); + return; + } + + Frame data; + if (queue_rx->dequeue(data) && data.identifier < 2048 && routes[data.identifier] != nullptr) { + routes[data.identifier]->handle(data); + } +} + +void Dispatcher::register_route(uint32_t id, IHandler* handler) { + if (id < 2048) routes[id] = handler; +} + +} // namespace CAN \ No newline at end of file diff --git a/lib/can/dispatcher.h b/lib/can/dispatcher.h index fbc6971..75f0ef8 100644 --- a/lib/can/dispatcher.h +++ b/lib/can/dispatcher.h @@ -2,8 +2,9 @@ #include #include +#include +#include -#include "../core/queue/queue.h" #include "types.h" class watchdog; @@ -12,20 +13,19 @@ namespace CAN { class Dispatcher { public: - const void send(const Frame* data); - const void digest_read(void *data); - // This means that the dispatcher owns the watchdogs, not sure if that is what we want - void register_route(std::unique_ptr watchdog); - void unregister_route(); + static Dispatcher& get_instance(); + Dispatcher(const Dispatcher&) = delete; + Dispatcher& operator=(const Dispatcher&) = delete; - const dispatcher* getInstance(); - dispatcher(const dispatcher&) = delete; - dispatcher& operator=(const dispatcher&) = delete; + void enqueue(const Frame& data); + void register_route(uint32_t id, IHandler* handler); + void dispatch(); + void set_queue(Core::IQueue* q) { queue_rx = q; } private: - dispatcher(); - Core::queue queue_rx; - Core::queue queue_tx; - std::map> node_routes; + Dispatcher(); + Core::IQueue* queue_rx; + // O(1) Lookup: Array of pointers to handlers (Size 2048 for 11-bit IDs) + IHandler* routes[2048]; }; } // namespace CAN \ No newline at end of file diff --git a/lib/can/esp32_s3_can_service.h b/lib/can/esp32_s3_can_service.h deleted file mode 100644 index c59a6d2..0000000 --- a/lib/can/esp32_s3_can_service.h +++ /dev/null @@ -1,81 +0,0 @@ - -#ifndef CAN_ESP32_S3_CAN_SERVICE_H -#define CAN_ESP32_S3_CAN_SERVICE_H - -#if defined(ESP32) - -#include "service.h" -#include -#include - -namespace CAN { - -class ESP32S3CanService : public Service { -public: - - const Result install_driver( - const GeneralConfig *g_config, - const TimingConfig *t_config, - const FilterConfig *f_config - ) override { - return (Result)twai_driver_install( - (twai_general_config_t*)g_config, - (twai_timing_config_t*)t_config, - (twai_filter_config_t*)f_config - ); - } - - const Result uninstall_driver() override { - return (Result)twai_driver_uninstall(); - } - - const Result start() override { - return (Result)twai_start(); - } - - const Result stop() override { - return (Result)twai_stop(); - } - - const Result transmit(const Frame *frame, Tick ticks_to_wait) override { - return (Result)twai_transmit((twai_message_t*)frame, ticks_to_wait); - } - -#endif // CAN_ESP32_S3_CAN_SERVICE_H - - const Result receive(Frame *frame, Tick ticks_to_wait) override { - return (Result)twai_receive((twai_message_t*)frame, ticks_to_wait); - } - - const Result alerts(Alert *alerts, Tick ticks_to_wait) override { - return (Result)twai_read_alerts(alerts, ticks_to_wait); - } - - const Result reconfigure_alerts(Alert alerts_to_enable, Alert *current_alerts) override { - return (Result)twai_reconfigure_alerts(alerts_to_enable, current_alerts); - } - - const Result initiate_recovery() override { - return (Result)twai_initiate_recovery(); - } - - const Result status_info(StatusInfo *status_info) override { - return (Result)twai_get_status_info((twai_status_info_t*)status_info); - } - - const Result clear_transmit_queue() override { - return (Result)twai_clear_transmit_queue(); - } - - const Result clear_receive_queue() override { - return (Result)twai_clear_receive_queue(); - } - - const Result reset_pin(const PIN pin) override { - return (Result)gpio_reset_pin((gpio_num_t)pin); - } -}; - -} // namespace CAN - -#endif // ESP32 diff --git a/lib/can/provider.cpp b/lib/can/provider.cpp deleted file mode 100644 index bf1a92c..0000000 --- a/lib/can/provider.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "provider.h" - -using namespace CAN; - -bool Provider::set_status() { - return service->status_info(&status) == Result::OK; -} - -bool Provider::install_driver() { - // Reset GPIO pins - service->reset_pin(transmit_pin); - service->reset_pin(receive_pin); - - // Configure TWAI driver - TimingConfig t_config = timing_config; - FilterConfig f_config = filter_config; - GeneralConfig g_config = GeneralConfig(transmit_pin, receive_pin, Mode::NORMAL); - - // Install and start TWAI driver - if (service->install_driver(&g_config, &t_config, &f_config) != Result::OK) { - // Failed to install TWAI driver - return false; - } - - return true; -} - -bool Provider::uninstall_driver() { - return service->uninstall_driver() == Result::OK; -} - -bool Provider::begin() { - // If already running, restart first - if (!end()) { - // Failed to end previous session - return false; - } - - if (!install_driver()) { - // Failed to install driver - return false; - } - - // Start TWAI driver - if (service->start() != Result::OK) { - // Failed to start TWAI driver - end(); - return false; - } - - // Set running state - is_running = true; - return true; -} - -bool Provider::recover() { - if (!set_status()) { - // Failed to get status - return false; - } - - switch (status.state) { - case State::BUS_OFF: - return service->initiate_recovery() == Result::OK; - case State::STOPPED: - return true; - case State::RECOVERING: - return true; - default: - return false; - } -} - -bool Provider::restart() { - if (!set_status()) { - // Failed to get status - return false; - } - - switch (status.state) { - case State::STOPPED: - // If stopped, start the driver - return service->start() == Result::OK; - default: - // For other states, restart is not applicable - return false; - } -} - -bool Provider::end() { - // Stop and uninstall TWAI driver - bool did_stop = service->stop() == Result::OK; - bool did_uninstall = uninstall_driver(); - is_running = false; - return did_stop && did_uninstall; -} - -bool Provider::transmit(const Frame& frame, uint32_t timeout) { - if (service->transmit(&frame, timeout) != Result::OK) { - return false; - } - return true; -} - -bool Provider::receive(Frame& frame, uint32_t timeout) { - if (service->receive(&frame, timeout) != Result::OK) { - return false; - } - return true; -} \ No newline at end of file diff --git a/lib/can/provider.h b/lib/can/provider.h deleted file mode 100644 index 83a4d03..0000000 --- a/lib/can/provider.h +++ /dev/null @@ -1,102 +0,0 @@ - -#ifndef CAN_PROVIDER_H -#define CAN_PROVIDER_H - -#include "service.h" -#include - -namespace CAN { - -/* - * Provider class for handling CAN operations. - */ -class Provider { -public: - // Indicates whether the CAN provider is currently running. - bool is_running = false; - // TWAI status information. - PIN transmit_pin; - // Receive pin for the CAN manager. - PIN receive_pin; - // Transmit queue size. - uint16_t transmit_queue_size = 5; - // Receive queue size. - uint16_t receive_queue_size = 5; - // Filter configuration for the CAN manager. - FilterConfig filter_config = FilterConfig(); // TWAI_FILTER_CONFIG_ACCEPT_ALL(); - // Timing configuration for the CAN manager. - TimingConfig timing_config = TimingConfig(); // TWAI_TIMING_CONFIG_500KBITS(); - // Status information for the CAN manager. - StatusInfo status; - - Provider(Service* service, PIN transmit_pin, PIN receive_pin) : service(service), transmit_pin(transmit_pin), receive_pin(receive_pin) {} - Provider(Service* service) : service(service), transmit_pin(UNUSED), receive_pin(UNUSED) {} - - ~Provider() = default; - - /* - * Initializes the CAN provider. - * @returns true if initialization was successful, false otherwise. - */ - bool begin(); - - /* - * Recovers the CAN provider from a bus-off state. - * @returns true if recovery was successful, false otherwise. - */ - bool recover(); - - /* - * Restarts the CAN provider. - * @returns true if restart was successful, false otherwise. - */ - bool restart(); - - /* - * Ends the CAN provider. - * @returns true if end was successful, false otherwise. - */ - bool end(); - - /* - * Transmits a CAN frame. - * @param frame The CAN frame to transmit. - * @param timeout The timeout for transmission in milliseconds. - * @returns true if transmission was successful, false otherwise. - */ - bool transmit(const Frame& frame, uint32_t timeout = 1000); - - /* - * Receives a CAN frame. - * @param frame The CAN frame to receive. - * @param timeout The timeout for reception in milliseconds. - * @returns true if reception was successful, false otherwise. - */ - bool receive(Frame& frame, uint32_t timeout = 1000); - - /* - * Installs the CAN driver. - * @returns true if installation was successful, false otherwise. - */ - bool install_driver(); - - /* - * Uninstalls the CAN driver. - * @returns true if uninstallation was successful, false otherwise. - */ - bool uninstall_driver(); - -private: - // Provides a wrapped implementation of the TWAI interface - Service* service; - - /* - * Sets the current status of the CAN provider. - * @returns true if status was set successfully, false otherwise. - */ - bool set_status(); -}; - -} // namespace CAN - -#endif // CAN_PROVIDER_H diff --git a/lib/can/service.h b/lib/can/service.h index ad4139d..2ed6c4b 100644 --- a/lib/can/service.h +++ b/lib/can/service.h @@ -7,42 +7,19 @@ #include "types.h" #include "dispatcher.h" -// TODO: Handle Errors - namespace CAN { -class Old_Service { -public: - virtual const Result install_driver(const GeneralConfig *g_config, const TimingConfig *t_config, const FilterConfig *f_config) = 0; - virtual const Result uninstall_driver() = 0; - virtual const Result start() = 0; - virtual const Result stop() = 0; - virtual const Result transmit(const Frame *frame, Tick ticks_to_wait) = 0; - virtual const Result receive(Frame *frame, Tick ticks_to_wait) = 0; - virtual const Result alerts(Alert *alerts, Tick ticks_to_wait) = 0; - virtual const Result reconfigure_alerts(Alert alerts_enabled, Alert *current_alerts) = 0; - virtual const Result initiate_recovery() = 0; - virtual const Result status_info(StatusInfo *status_info) = 0; - virtual const Result clear_transmit_queue() = 0; - virtual const Result clear_receive_queue() = 0; - virtual const Result reset_pin(const PIN pin) = 0; - - virtual ~Old_Service() = default; -}; - class Service { public: - virtual void setup() = 0; - virtual void stop_listening() = 0; - virtual void start_listening() = 0; - virtual void digest_read() = 0; // Grabs from dispatcher - virtual void raw_send(void *data) = 0; - virtual void raw_read(void *data) = 0; - virtual void recover() = 0; + virtual bool setup(const void * config) = 0; + virtual bool tear_down() = 0; + virtual bool send(const Frame& frame) = 0; + virtual const Frame* read() = 0; + virtual bool recover() = 0; ~Service() = default; private: - std::shared_ptr m_dispatcher; // dispatcher is a singleton, so Service doesn't own it + Dispatcher& dispatcher = Dispatcher::get_instance(); }; diff --git a/lib/can/transmitter.cpp b/lib/can/transmitter.cpp new file mode 100644 index 0000000..98706ed --- /dev/null +++ b/lib/can/transmitter.cpp @@ -0,0 +1,47 @@ +#include "transmitter.h" + +namespace CAN { + +Transmitter& Transmitter::get_instance() { + static Transmitter instance; + return instance; +} + +void Transmitter::set_service(Service* s) { + service = s; +} + +void Transmitter::set_queue(Core::IQueue* queue) { + queue_tx = queue; +} + +bool Transmitter::send(const Frame& frame) { + if (service == nullptr) { + // Service not set, cannot send + LOG_ERR("Transmitter", "Service not set, cannot send frame with ID: %u", frame.identifier); + return false; + } + + return service->send(frame); +} + +void Transmitter::transmit() { + if (queue_tx == nullptr) { + // Service or queue not set, cannot process + LOG_ERR("Transmitter", "Queue not set, cannot transmit frames"); + return; + } + + if (service == nullptr) { + // Service or queue not set, cannot process + LOG_ERR("Transmitter", "Service not set, cannot transmit frames"); + return; + } + + Frame frame; + if (queue_tx->dequeue(frame)) { + service->send(frame); + } +}; + +} // namespace CAN \ No newline at end of file diff --git a/lib/can/transmitter.h b/lib/can/transmitter.h new file mode 100644 index 0000000..5a0f379 --- /dev/null +++ b/lib/can/transmitter.h @@ -0,0 +1,31 @@ +#ifndef CAN_TRANSMITTER_H +#define CAN_TRANSMITTER_H + +#include +#include + +#include "service.h" +#include "types.h" + +namespace CAN { + +class Transmitter { +public: + static Transmitter& get_instance(); + Transmitter(const Transmitter&) = delete; + Transmitter& operator=(const Transmitter&) = delete; + + bool send(const Frame& frame); + void set_service(Service* service); + void set_queue(Core::IQueue* queue); + void transmit(); + +private: + Transmitter(); + Core::IQueue* queue_tx; + Service* service; +}; + +} + +#endif // CAN_TRANSMITTER_H \ No newline at end of file diff --git a/lib/can/types.h b/lib/can/types.h index addfa04..85138df 100644 --- a/lib/can/types.h +++ b/lib/can/types.h @@ -6,95 +6,13 @@ namespace CAN { -typedef uint32_t Tick; -typedef uint32_t Alert; - -enum class Result { - OK = 0, /*!< esp_err_t value indicating success (no error) */ - FAIL = -1, /*!< Generic esp_err_t code indicating failure */ - - ERR_NO_MEM = 0x101, /*!< Out of memory */ - ERR_INVALID_ARG = 0x102, /*!< Invalid argument */ - ERR_INVALID_STATE = 0x103, /*!< Invalid state */ - ERR_INVALID_SIZE = 0x104, /*!< Invalid size */ - ERR_NOT_FOUND = 0x105, /*!< Requested resource not found */ - ERR_NOT_SUPPORTED = 0x106, /*!< Operation or feature not supported */ - ERR_TIMEOUT = 0x107, /*!< Operation timed out */ - ERR_INVALID_RESPONSE = 0x108, /*!< Received response was invalid */ - ERR_INVALID_CRC = 0x109, /*!< CRC or checksum was invalid */ - ERR_INVALID_VERSION = 0x10A, /*!< Version was invalid */ - ERR_INVALID_MAC = 0x10B, /*!< MAC address was invalid */ - ERR_NOT_FINISHED = 0x10C, /*!< There are items remained to retrieve */ - - - ERR_WIFI_BASE = 0x3000, /*!< Starting number of WiFi error codes */ - ERR_MESH_BASE = 0x4000, /*!< Starting number of MESH error codes */ - ERR_FLASH_BASE = 0x6000, /*!< Starting number of flash error codes */ - ERR_HW_CRYPTO_BASE = 0xc000, /*!< Starting number of HW cryptography module error codes */ - ERR_MEMPROT_BASE = 0xd000 /*!< Starting number of Memory Protection API error codes */ -}; - -enum class State { - STOPPED, /**< Stopped state. The TWAI controller will not participate in any TWAI bus activities */ - RUNNING, /**< Running state. The TWAI controller can transmit and receive messages */ - BUS_OFF, /**< Bus-off state. The TWAI controller cannot participate in bus activities until it has recovered */ - RECOVERING, /**< Recovering state. The TWAI controller is undergoing bus recovery */ -}; - -enum class Mode { - NORMAL, /**< Normal operating mode where TWAI controller can send/receive/acknowledge messages */ - NO_ACK, /**< Transmission does not require acknowledgment. Use this mode for self testing */ - LISTEN_ONLY, /**< The TWAI controller will not influence the bus (No transmissions or acknowledgments) but can receive messages */ +enum ServiceConfigType { + TWAI }; -enum PIN { - UNUSED = -1, /*!< Use to signal not connected to S/W */ - NUM_0 = 0, /*!< GPIO0, input and output */ - NUM_1 = 1, /*!< GPIO1, input and output */ - NUM_2 = 2, /*!< GPIO2, input and output */ - NUM_3 = 3, /*!< GPIO3, input and output */ - NUM_4 = 4, /*!< GPIO4, input and output */ - NUM_5 = 5, /*!< GPIO5, input and output */ - NUM_6 = 6, /*!< GPIO6, input and output */ - NUM_7 = 7, /*!< GPIO7, input and output */ - NUM_8 = 8, /*!< GPIO8, input and output */ - NUM_9 = 9, /*!< GPIO9, input and output */ - NUM_10 = 10, /*!< GPIO10, input and output */ - NUM_11 = 11, /*!< GPIO11, input and output */ - NUM_12 = 12, /*!< GPIO12, input and output */ - NUM_13 = 13, /*!< GPIO13, input and output */ - NUM_14 = 14, /*!< GPIO14, input and output */ - NUM_15 = 15, /*!< GPIO15, input and output */ - NUM_16 = 16, /*!< GPIO16, input and output */ - NUM_17 = 17, /*!< GPIO17, input and output */ - NUM_18 = 18, /*!< GPIO18, input and output */ - NUM_19 = 19, /*!< GPIO19, input and output */ - NUM_20 = 20, /*!< GPIO20, input and output */ - NUM_21 = 21, /*!< GPIO21, input and output */ - NUM_26 = 26, /*!< GPIO26, input and output */ - NUM_27 = 27, /*!< GPIO27, input and output */ - NUM_28 = 28, /*!< GPIO28, input and output */ - NUM_29 = 29, /*!< GPIO29, input and output */ - NUM_30 = 30, /*!< GPIO30, input and output */ - NUM_31 = 31, /*!< GPIO31, input and output */ - NUM_32 = 32, /*!< GPIO32, input and output */ - NUM_33 = 33, /*!< GPIO33, input and output */ - NUM_34 = 34, /*!< GPIO34, input and output */ - NUM_35 = 35, /*!< GPIO35, input and output */ - NUM_36 = 36, /*!< GPIO36, input and output */ - NUM_37 = 37, /*!< GPIO37, input and output */ - NUM_38 = 38, /*!< GPIO38, input and output */ - NUM_39 = 39, /*!< GPIO39, input and output */ - NUM_40 = 40, /*!< GPIO40, input and output */ - NUM_41 = 41, /*!< GPIO41, input and output */ - NUM_42 = 42, /*!< GPIO42, input and output */ - NUM_43 = 43, /*!< GPIO43, input and output */ - NUM_44 = 44, /*!< GPIO44, input and output */ - NUM_45 = 45, /*!< GPIO45, input and output */ - NUM_46 = 46, /*!< GPIO46, input and output */ - NUM_47 = 47, /*!< GPIO47, input and output */ - NUM_48 = 48, /*!< GPIO48, input and output */ - NUM_MAX, +struct BaseServiceConfig { + ServiceConfigType type; + explicit BaseServiceConfig(ServiceConfigType t) : type(t) {} }; struct Frame { @@ -153,72 +71,10 @@ struct Frame { } }; -struct FilterConfig { - uint32_t acceptance_code; /**< 32-bit acceptance code */ - uint32_t acceptance_mask; /**< 32-bit acceptance mask */ - bool single_filter; /**< Use Single Filter Mode (see documentation) */ - - FilterConfig() { - acceptance_code = 0; - acceptance_mask = 0xFFFFFFFF; - single_filter = true; - } -}; - -struct TimingConfig { - uint32_t brp; /**< Baudrate prescaler (i.e., APB clock divider). Any even number from 2 to 128 for ESP32, 2 to 32768 for ESP32S2. - For ESP32 Rev 2 or later, multiples of 4 from 132 to 256 are also supported */ - uint8_t tseg_1; /**< Timing segment 1 (Number of time quanta, between 1 to 16) */ - uint8_t tseg_2; /**< Timing segment 2 (Number of time quanta, 1 to 8) */ - uint8_t sjw; /**< Synchronization Jump Width (Max time quanta jump for synchronize from 1 to 4) */ - bool triple_sampling; /**< Enables triple sampling when the TWAI controller samples a bit */ - - TimingConfig() { - brp = 8; - tseg_1 = 15; - tseg_2 = 4; - sjw = 3; - triple_sampling = false; - } -}; - -struct StatusInfo { - State state; /**< Current state of TWAI controller (Stopped/Running/Bus-Off/Recovery) */ - uint32_t msgs_to_tx; /**< Number of messages queued for transmission or awaiting transmission completion */ - uint32_t msgs_to_rx; /**< Number of messages in RX queue waiting to be read */ - uint32_t tx_error_counter; /**< Current value of Transmit Error Counter */ - uint32_t rx_error_counter; /**< Current value of Receive Error Counter */ - uint32_t tx_failed_count; /**< Number of messages that failed transmissions */ - uint32_t rx_missed_count; /**< Number of messages that were lost due to a full RX queue (or errata workaround if enabled) */ - uint32_t rx_overrun_count; /**< Number of messages that were lost due to a RX FIFO overrun */ - uint32_t arb_lost_count; /**< Number of instances arbitration was lost */ - uint32_t bus_error_count; /**< Number of instances a bus error has occurred */ -}; - -struct GeneralConfig { - Mode mode; /**< Mode of TWAI controller */ - PIN tx_io; /**< Transmit GPIO number */ - PIN rx_io; /**< Receive GPIO number */ - PIN clkout_io; /**< CLKOUT GPIO number (optional, set to -1 if unused) */ - PIN bus_off_io; /**< Bus off indicator GPIO number (optional, set to -1 if unused) */ - uint32_t tx_queue_len; /**< Number of messages TX queue can hold (set to 0 to disable TX Queue) */ - uint32_t rx_queue_len; /**< Number of messages RX queue can hold */ - uint32_t alerts_enabled; /**< Bit field of alerts to enable (see documentation) */ - uint32_t clkout_divider; /**< CLKOUT divider. Can be 1 or any even number from 2 to 14 (optional, set to 0 if unused) */ - int intr_flags; /**< Interrupt flags to set the priority of the driver's ISR. Note that to use the ESP_INTR_FLAG_IRAM, the CONFIG_TWAI_ISR_IN_IRAM option should be enabled first. */ - - GeneralConfig(PIN transmit_pin, PIN recieve_pin, Mode mode) { - mode = mode; - tx_io = transmit_pin; - rx_io = recieve_pin; - clkout_io = UNUSED; - bus_off_io = UNUSED; - tx_queue_len = 5; - rx_queue_len = 5; - alerts_enabled = 0; // NONE - clkout_divider = 0; - intr_flags = (1<<1); // LEVEL 1 - } +class IHandler { +public: + virtual void handle(const Frame& data) = 0; + virtual ~IHandler() = default; }; } // namespace CAN diff --git a/lib/core/core.h b/lib/core/core.h index 00592c7..6ec9b0b 100644 --- a/lib/core/core.h +++ b/lib/core/core.h @@ -4,5 +4,7 @@ #include "lock.h" #include "thread.h" +#include "queue.h" +#include "logger.h" #endif // CORE_H \ No newline at end of file diff --git a/lib/core/logger.h b/lib/core/logger.h new file mode 100644 index 0000000..88bbdf5 --- /dev/null +++ b/lib/core/logger.h @@ -0,0 +1,8 @@ +#ifndef CORE_LOGGER_UMBRELLA_H +#define CORE_LOGGER_UMBRELLA_H + +#include "logger/i_logger.h" +#include "logger/types.h" +#include "logger/logger.h" + +#endif // CORE_LOGGER_UMBRELLA_H \ No newline at end of file diff --git a/lib/core/logger/i_logger.h b/lib/core/logger/i_logger.h new file mode 100644 index 0000000..785ca52 --- /dev/null +++ b/lib/core/logger/i_logger.h @@ -0,0 +1,29 @@ +#ifndef CORE_LOGGER_I_LOGGER_H +#define CORE_LOGGER_I_LOGGER_H + +#include "types.h" + +namespace Core { + +/** + * @brief Interface for hardware-specific log outputs (Serial, SD Card, etc.) + */ +class ILogger { +public: + virtual ~ILogger() = default; + + /** + * @brief Consumes a pre-filled LogEntry. + * @param entry The entry containing msg, timestamp, file, line, and level. + */ + virtual void log(const LogEntry& entry) = 0; + + /** + * @brief Optional: Flush any pending writes (useful for SD cards before a crash) + */ + virtual void flush() {} +}; + +} // namespace Core + +#endif // CORE_LOGGER_I_LOGGER_H \ No newline at end of file diff --git a/lib/core/logger/logger.h b/lib/core/logger/logger.h new file mode 100644 index 0000000..3cc81fa --- /dev/null +++ b/lib/core/logger/logger.h @@ -0,0 +1,77 @@ +#ifndef CORE_LOGGER_H +#define CORE_LOGGER_H + +#include +#include +#include +#include + +#include "i_logger.h" + +namespace Core { + +class Logger { +public: + // This is the underlying function the Macro will call + static void log(LogLevel level, const char* file, int line, const char* tag, const char* format, ...) { + LogEntry entry; + entry.level = level; + entry.file = file; + entry.line = line; + entry.timestamp = get_system_millis(); // Implement based on ESP32 or STM32 + + strncpy(entry.tag, tag, sizeof(entry.tag)); + + va_list args; + va_start(args, format); + vsnprintf(entry.msg, sizeof(entry.msg), format, args); + va_end(args); + + // Fire and forget: Push to the non-blocking queue + get_instance()._queue->enqueue(entry); + } + + static void set_output(ILogger* backend) { + get_instance()._backend = backend; + } + + static void set_queue(IQueue* queue) { + get_instance()._queue = queue; + } + + // Call this from a low-priority background task/thread + static void process() { + LogEntry entry; + // Block until a log arrives + if (get_instance()._queue->dequeue(entry, 100)) { + if (get_instance()._backend) { + // The backend handles the actual string printing + get_instance()._backend->log(entry); + } + } + } + +private: + Logger(); + static Logger& get_instance() { + static Logger instance; + return instance; + } + + IQueue* _queue; + ILogger* _backend = nullptr; + + // Platform specific: millis() for Arduino/ESP32 or HAL_GetTick() for STM32 + static uint32_t get_system_millis(); +}; + +} // namespace Core + +// --- THE MACRO INTERFACE --- +// This replaces the call site with the correct File and Line info +#define LOG_DEBUG(tag, fmt, ...) Core::Logger::log(Core::LogLevel::DEBUG, __FILE__, __LINE__, tag, fmt, ##__VA_ARGS__) +#define LOG_INFO(tag, fmt, ...) Core::Logger::log(Core::LogLevel::INFO, __FILE__, __LINE__, tag, fmt, ##__VA_ARGS__) +#define LOG_WARN(tag, fmt, ...) Core::Logger::log(Core::LogLevel::WARNING, __FILE__, __LINE__, tag, fmt, ##__VA_ARGS__) +#define LOG_ERR(tag, fmt, ...) Core::Logger::log(Core::LogLevel::CRITICAL, __FILE__, __LINE__, tag, fmt, ##__VA_ARGS__) + +#endif // CORE_LOGGER_H \ No newline at end of file diff --git a/lib/core/logger/types.h b/lib/core/logger/types.h new file mode 100644 index 0000000..e46a655 --- /dev/null +++ b/lib/core/logger/types.h @@ -0,0 +1,26 @@ +#ifndef CORE_LOGGER_TYPES_H +#define CORE_LOGGER_TYPES_H + +#include + +namespace Core { + +enum class LogLevel { + DEBUG, + INFO, + WARNING, + CRITICAL +}; + +struct LogEntry { + LogLevel level; + uint32_t timestamp; // System millis + int line; + const char* file; // Pointer to string literal (Flash) + char tag[16]; + char msg[64]; // Fixed size to avoid heap allocation +}; + +} // namespace Core + +#endif // CORE_LOGGER_TYPES_H \ No newline at end of file diff --git a/lib/core/queue.h b/lib/core/queue.h new file mode 100644 index 0000000..a009724 --- /dev/null +++ b/lib/core/queue.h @@ -0,0 +1,6 @@ +#ifndef QUEUE_H +#define QUEUE_H + +#include "queue/i_queue.h" + +#endif // QUEUE_H \ No newline at end of file diff --git a/lib/core/queue/i_queue.h b/lib/core/queue/i_queue.h new file mode 100644 index 0000000..d5f2e00 --- /dev/null +++ b/lib/core/queue/i_queue.h @@ -0,0 +1,34 @@ +#ifndef CORE_I_QUEUE_H +#define CORE_I_QUEUE_H + +#include +#include + +namespace Core { + +template +class IQueue { +public: + virtual ~IQueue() = default; + + /** + * @brief Adds an item to the queue. + * @param data The item to copy into the queue. + * @return true if successful, false if the queue is full. + */ + virtual void enqueue(const T& data) = 0; + + /** + * @brief Removes an item from the queue. + * @param outData Reference to store the popped item. + * @param timeout_ms How long to wait if the queue is empty (0 for non-blocking). + * @return true if an item was retrieved, false if it timed out. + */ + virtual bool dequeue(T& data, uint32_t timeout_ms = 0) = 0; + virtual size_t size() const = 0; + virtual bool is_full() const = 0; +}; + +} // namespace Core + +#endif // CORE_I_QUEUE_H \ No newline at end of file diff --git a/lib/core/queue/queue.h b/lib/core/queue/queue.h deleted file mode 100644 index a54423d..0000000 --- a/lib/core/queue/queue.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -namespace Core { - -class queue { -public: - virtual void insert(void* data); - virtual void remove(void* data); -}; - -} // namespace Core \ No newline at end of file diff --git a/test/mocks/can/mock_can_service.cpp b/test/mocks/can/mock_can_service.cpp new file mode 100644 index 0000000..b9eadbc --- /dev/null +++ b/test/mocks/can/mock_can_service.cpp @@ -0,0 +1,33 @@ +#include "mock_can_service.h" + +namespace MOCKS { + +bool MockCanService::setup(const void * config) { + return true; +} + +bool MockCanService::tear_down() { + return true; +} + +void MockCanService::stop_listening() { + +} + +void MockCanService::start_listening() { + +} + +bool MockCanService::send(Frame* frame) { + return true; +} + +const Frame* MockCanService::read() { + return nullptr; +} + +bool MockCanService::recover() { + return true; +} + +} // namespace MOCKS \ No newline at end of file diff --git a/test/mocks/can/mock_can_service.h b/test/mocks/can/mock_can_service.h index 4312139..cb5e01b 100644 --- a/test/mocks/can/mock_can_service.h +++ b/test/mocks/can/mock_can_service.h @@ -1,145 +1,24 @@ #ifndef MOCK_CAN_SERVICE_H #define MOCK_CAN_SERVICE_H -#include -#include -#include +#include using namespace CAN; +using namespace std; namespace MOCKS { class MockCanService : public Service { public: - std::function - on_install_driver = - [](const GeneralConfig*, const TimingConfig*, const FilterConfig*) { return Result::OK; }; - - std::function on_uninstall_driver = - []() { return Result::OK; }; - - std::function on_start = - []() { return Result::OK; }; - - std::function on_stop = - []() { return Result::OK; }; - - std::function - on_transmit = - [](const Frame*, Tick) { return Result::OK; }; - - std::function - on_receive = - [](Frame*, Tick) { return Result::OK; }; - - std::function - on_alerts = - [](Alert*, Tick) { return Result::OK; }; - - std::function - on_reconfigure_alerts = - [](Alert, Alert*) { return Result::OK; }; - - std::function on_initiate_recovery = - []() { return Result::OK; }; - - std::function - on_status_info = - [](StatusInfo*) { return Result::OK; }; - - std::function on_clear_transmit_queue = - []() { return Result::OK; }; - - std::function on_clear_receive_queue = - []() { return Result::OK; }; - - std::function - on_reset_pin = - [](PIN) { return Result::OK; }; - - struct CallCounts { - int install_driver = 0; - int uninstall_driver = 0; - int start = 0; - int stop = 0; - int transmit = 0; - int receive = 0; - int alerts = 0; - int reconfigure_alerts = 0; - int initiate_recovery = 0; - int status_info = 0; - int clear_transmit_queue = 0; - int clear_receive_queue = 0; - int reset_pin = 0; - } calls; - - const Result install_driver(const GeneralConfig *gc, - const TimingConfig *tc, - const FilterConfig *fc) override - { - calls.install_driver++; - return on_install_driver(gc, tc, fc); - } - - const Result uninstall_driver() override { - calls.uninstall_driver++; - return on_uninstall_driver(); - } - - const Result start() override { - calls.start++; - return on_start(); - } - - const Result stop() override { - calls.stop++; - return on_stop(); - } - - const Result transmit(const Frame *frame, Tick wait) override { - calls.transmit++; - return on_transmit(frame, wait); - } - - const Result receive(Frame *frame, Tick wait) override { - calls.receive++; - return on_receive(frame, wait); - } - - const Result alerts(Alert *alerts, Tick wait) override { - calls.alerts++; - return on_alerts(alerts, wait); - } - - const Result reconfigure_alerts(Alert a, Alert *c) override { - calls.reconfigure_alerts++; - return on_reconfigure_alerts(a, c); - } - - const Result initiate_recovery() override { - calls.initiate_recovery++; - return on_initiate_recovery(); - } - - const Result status_info(StatusInfo *si) override { - calls.status_info++; - return on_status_info(si); - } - - const Result clear_transmit_queue() override { - calls.clear_transmit_queue++; - return on_clear_transmit_queue(); - } - - const Result clear_receive_queue() override { - calls.clear_receive_queue++; - return on_clear_receive_queue(); - } - - const Result reset_pin(const PIN pin) override { - calls.reset_pin++; - return on_reset_pin(pin); - } + bool setup(const void * config) = 0; + bool tear_down() = 0; + void stop_listening() = 0; + void start_listening() = 0; + bool send(Frame* frame) = 0; + const Frame* read() = 0; + bool recover() = 0; + + ~Service() = default; }; } // namespace MOCKS diff --git a/test/mocks/logger/serial_logger.h b/test/mocks/logger/serial_logger.h new file mode 100644 index 0000000..89201fb --- /dev/null +++ b/test/mocks/logger/serial_logger.h @@ -0,0 +1,33 @@ +#ifndef CORE_LOGGER_SERIAL_LOGGER_H +#define CORE_LOGGER_SERIAL_LOGGER_H + +#include +#include "core/logger.h" + +class SerialLogger : public Core::ILogger { +public: + void log(const Core::LogEntry& e) override { + // Format: [Timestamp] [LEVEL] [TAG] [File:Line] Message + printf( + "[%lu] [%s] [%s] [%s:%d] %s\n", + e.timestamp, + levelToString(e.level), + e.tag, + e.file, + e.line, + e.msg + ); + } + +private: + const char* levelToString(Core::LogLevel l) { + switch(l) { + case Core::LogLevel::INFO: return "INFO"; + case Core::LogLevel::WARN: return "WARN"; + case Core::LogLevel::ERR: return "ERROR"; + default: return "DEBUG"; + } + } +}; + +#endif // CORE_LOGGER_SERIAL_LOGGER_H \ No newline at end of file diff --git a/test/test_can/test_codables.cpp b/test/test_can/test_frame.cpp similarity index 89% rename from test/test_can/test_codables.cpp rename to test/test_can/test_frame.cpp index d54515d..4c3dd40 100644 --- a/test/test_can/test_codables.cpp +++ b/test/test_can/test_frame.cpp @@ -13,7 +13,7 @@ struct TestMessage { uint64_t a_uint32: 32; }; -void test_can_encode() { +void test_encode() { TestMessage message = {}; message.a_bool = true; message.a_uint8 = 0xAB; @@ -34,7 +34,7 @@ void test_can_encode() { TEST_ASSERT_EQUAL(0x12, frame.data[7]); } -void test_can_decode() { +void test_decode() { uint8_t data[8] = { 0x01, 0xAB, 0xEF, 0xCD, 0x78, 0x56, 0x34, 0x12 }; Frame frame(0, data); @@ -46,7 +46,7 @@ void test_can_decode() { TEST_ASSERT_EQUAL(0x12345678, message->a_uint32); } -void test_can_encode_and_decode() { +void test_encode_and_decode() { TestMessage original_message; original_message.a_bool = true; original_message.a_uint8 = 0xAB; @@ -63,8 +63,8 @@ void test_can_encode_and_decode() { TEST_ASSERT_EQUAL(original_message.a_uint32, decoded_message->a_uint32); } -void run_can_coding_tests() { - RUN_TEST(test_can_encode); - RUN_TEST(test_can_decode); - RUN_TEST(test_can_encode_and_decode); +void run_frame_tests() { + RUN_TEST(test_encode); + RUN_TEST(test_decode); + RUN_TEST(test_encode_and_decode); } \ No newline at end of file diff --git a/test/test_can/test_main.cpp b/test/test_can/test_main.cpp index 4606260..eaccf37 100644 --- a/test/test_can/test_main.cpp +++ b/test/test_can/test_main.cpp @@ -13,7 +13,6 @@ void tearDown(void) { // Reuse the module main pattern and call all can tests int main() { UNITY_BEGIN(); - run_manager_can_tests(); - run_can_coding_tests(); + run_frame_tests(); return UNITY_END(); } \ No newline at end of file diff --git a/test/test_can/test_main.h b/test/test_can/test_main.h index dd2cfc5..127862b 100644 --- a/test/test_can/test_main.h +++ b/test/test_can/test_main.h @@ -3,7 +3,6 @@ #include -void run_manager_can_tests(); -void run_can_coding_tests(); +void run_frame_tests(); #endif // TEST_MAIN_H \ No newline at end of file diff --git a/test/test_can/test_provider.cpp b/test/test_can/test_provider.cpp deleted file mode 100644 index d51669c..0000000 --- a/test/test_can/test_provider.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include -#include -#include - -#include "test_main.h" - -using namespace CAN; -using namespace MOCKS; - -struct Bundle { - Provider provider; - MockCanService* service; -}; - -inline Bundle make_bundle() { - auto* service = new MockCanService(); - Provider provider(service); - - return { provider, service }; -} - -void test_can_begin() { - Bundle bundle = make_bundle(); - bool result = bundle.provider.begin(); - TEST_ASSERT_TRUE(result); -} - -void test_can_recover() { - Bundle bundle = make_bundle(); - - bundle.service->on_status_info = [](StatusInfo* status_info) { - status_info->state = State::BUS_OFF; - return Result::OK; - }; - - bool result = bundle.provider.recover(); - TEST_ASSERT_TRUE(result); -} - -void test_can_restart() { - Bundle bundle = make_bundle(); - - bundle.service->on_status_info = [](StatusInfo* status_info) { - status_info->state = State::STOPPED; - return Result::OK; - }; - - bool result = bundle.provider.restart(); - TEST_ASSERT_TRUE(result); -} - -void test_can_end() { - Bundle bundle = make_bundle(); - bool result = bundle.provider.end(); - TEST_ASSERT_TRUE(result); -} - -void test_can_transmit() { - Bundle bundle = make_bundle(); - Frame frame; - bool result = bundle.provider.transmit(frame); - TEST_ASSERT_TRUE(result); -} - -void test_can_receive() { - Bundle bundle = make_bundle(); - Frame frame; - bool result = bundle.provider.receive(frame); - TEST_ASSERT_TRUE(result); -} - -void run_manager_can_tests() { - RUN_TEST(test_can_begin); - RUN_TEST(test_can_recover); - RUN_TEST(test_can_restart); - RUN_TEST(test_can_end); - RUN_TEST(test_can_transmit); - RUN_TEST(test_can_receive); -} \ No newline at end of file From 9f1e9c9a15511ff4f80a041ebf4e0cf6fee2a752 Mon Sep 17 00:00:00 2001 From: Porter McGary <56183563+mcgaryp@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:16:45 -0600 Subject: [PATCH 03/13] Refactor TWAI service to use ENV_ESP32 macro and remove unused main files --- lib/can/TWAI/twai_service.cpp | 2 +- lib/can/TWAI/twai_service.h | 4 ++-- src/main.cpp | 5 ----- src/main.h | 19 ------------------- 4 files changed, 3 insertions(+), 27 deletions(-) delete mode 100644 src/main.cpp delete mode 100644 src/main.h diff --git a/lib/can/TWAI/twai_service.cpp b/lib/can/TWAI/twai_service.cpp index d71b4e1..3da5bf0 100644 --- a/lib/can/TWAI/twai_service.cpp +++ b/lib/can/TWAI/twai_service.cpp @@ -1,4 +1,4 @@ -#if defined(ESP32) +#if defined(ENV_ESP32) #include "twai_service.h" diff --git a/lib/can/TWAI/twai_service.h b/lib/can/TWAI/twai_service.h index ce0513c..75207d0 100644 --- a/lib/can/TWAI/twai_service.h +++ b/lib/can/TWAI/twai_service.h @@ -1,7 +1,7 @@ #ifndef TWAI_SERVICE_H #define TWAI_SERVICE_H -#if defined(ESP32) +#if defined(ENV_ESP32) #include #include @@ -28,6 +28,6 @@ class TWAIService : public Service { ~TWAIService() = default; }; -#endif // ESP32 +#endif // ENV_ESP32 #endif // TWAI_SERVICE_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 1e2c5b8..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "main.h" - -int main(void) { - return 0; -} \ No newline at end of file diff --git a/src/main.h b/src/main.h deleted file mode 100644 index 191954a..0000000 --- a/src/main.h +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __cplusplus -} -#endif \ No newline at end of file From 747d837fff96f566b2763f57992a6ea33800a24d Mon Sep 17 00:00:00 2001 From: Porter McGary <56183563+mcgaryp@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:12:31 -0600 Subject: [PATCH 04/13] Refactor CAN module: remove TWAI service and enhance dispatcher, service, and logger interfaces with detailed documentation --- lib/can/TWAI/twai_service.cpp | 174 ---------------------------- lib/can/TWAI/twai_service.h | 33 ------ lib/can/dispatcher.h | 75 +++++++++++- lib/can/service.h | 35 ++++++ lib/can/transmitter.h | 45 +++++++ lib/can/types.h | 37 ++++++ lib/core/logger/i_logger.h | 3 + lib/core/logger/logger.h | 87 +++++++++++--- lib/core/logger/types.h | 6 + lib/core/queue/i_queue.h | 13 +++ lib/core/task.h | 6 + lib/core/task/controller.h | 71 ++++++++++++ lib/core/thread/i_thread_strategy.h | 2 + 13 files changed, 363 insertions(+), 224 deletions(-) delete mode 100644 lib/can/TWAI/twai_service.cpp delete mode 100644 lib/can/TWAI/twai_service.h create mode 100644 lib/core/task.h create mode 100644 lib/core/task/controller.h diff --git a/lib/can/TWAI/twai_service.cpp b/lib/can/TWAI/twai_service.cpp deleted file mode 100644 index 3da5bf0..0000000 --- a/lib/can/TWAI/twai_service.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#if defined(ENV_ESP32) - -#include "twai_service.h" - -using namespace CORE; - -// This is a link to the documentation for the ESP32 TWAI driver, which is what this service is built on top of. -// https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/api-reference/peripherals/twai.html - -bool TWAIService::setup(const void * config) { - if (!config) return false; - - // 1. Validate Config Type - const BaseConfig* base = static_cast(config); - if (base->type != ServiceConfigType::TWAI) return false; - - // 2. Ensure a clean state - // We don't care if tear_down "fails," we just want the driver uninstalled. - tear_down(); - - const TWAIConfig* twai_config = static_cast(config); - - // 3. Install Driver - // This allocates the RX/TX Ring Buffers in internal RAM. - esp_err_t install_res = twai_driver_install( - twai_config->general_config, - twai_config->timing_config, - twai_config->filter_config - ); - - if (install_res != ESP_OK) { - // Log: install_res (e.g., out of memory or invalid pins) - LOG_ERR("TWAIService", "Failed to install TWAI driver: %d", install_res); - return false; - } - - // 4. Start the Driver - // This transitions the hardware from 'Stopped' to 'Running' (Alert/Listen mode) - esp_err_t start_res = twai_start(); - if (start_res != ESP_OK) { - LOG_ERR("TWAIService", "Failed to start TWAI driver: %d", start_res); - esp_err_t uninstall_res = twai_driver_uninstall(); // Cleanup if start fails - if (uninstall_res != ESP_OK) { - LOG_ERR("TWAIService", "Failed to uninstall TWAI driver after failed start: %d", uninstall_res); - } - return false; - } - - return true; -} - -bool TWAIService::tear_down() { - twai_status_info_t status; - // If the driver is not installed, twai_get_status_info returns ESP_ERR_INVALID_STATE - if (get_status(status) == ESP_ERR_INVALID_STATE) { - return true; - } - - // 1. If we are running or recovering, we must STOP first - if (status.state == TWAI_STATE_RUNNING || status.state == TWAI_STATE_RECOVERING) { - esp_err_t stop_res = twai_stop(); - if (stop_res != ESP_OK) { - // Log: Failed to stop, but we will try to uninstall anyway - LOG_ERR("TWAIService", "Failed to stop TWAI driver during tear_down: %d", stop_res); - } - } - - // 2. Uninstall the driver to free up the RX/TX ring buffers (RAM) - esp_err_t uninstall_res = twai_driver_uninstall(); - if (uninstall_res != ESP_OK) { - // Log: Fatal error during uninstall - LOG_ERR("TWAIService", "Failed to uninstall TWAI driver: %d", uninstall_res); - return false; - } - - return true; -} - -bool TWAIService::send(Frame& frame) { - twai_status_info_t status; - if (get_status(status) != ESP_OK || status.state != TWAI_STATE_RUNNING) { - return false; - } - - twai_message_t twai_frame; - twai_frame.identifier = frame.identifier; - twai_frame.data_length_code = frame.data_length_code; - for (int i = 0; i < frame.data_length_code; i++) { - twai_frame.data[i] = frame.data[i]; - } - - esp_err_t result = twai_transmit(twai_frame, portMAX_DELAY); - if (result != ESP_OK) { - LOG_ERR("TWAIService", "Failed to transmit TWAI frame: %d", result); - return false; - } - - return true; -} - -const Frame* TWAIService::read() { - twai_status_info_t status; - if (get_status(status) != ESP_OK || status.state != TWAI_STATE_RUNNING) { - return nullptr; - } - - twai_message_t twai_msg; - // Use a small timeout or portMAX_DELAY depending on your Dispatcher task strategy - esp_err_t result = twai_receive(&twai_msg, 100 / portTICK_PERIOD_MS); - if (result != ESP_OK) { - if (result != ESP_ERR_TIMEOUT) { - LOG_ERR("TWAIService", "Failed to receive TWAI frame: %d", result); - } - return nullptr; - } - - // IMPORTANT: Avoid 'Frame* frame = {};' as that is a null pointer. - // Use a static or member variable to return the address, or better yet, - // change the interface to 'bool read(Frame& outFrame)'. - static Frame frame_buffer; - frame_buffer.identifier = twai_msg.identifier; - frame_buffer.data_length_code = twai_msg.data_length_code; - memcpy(frame_buffer.data, twai_msg.data, twai_msg.data_length_code); - - return &frame_buffer; -} - -bool TWAIService::recover() { - twai_status_info_t status; - if (get_status(status) != ESP_OK) { - return false; - } - - switch (status.state) { - case TWAI_STATE_BUS_OFF: - // The hardware is 'dead'. Start the 128-pulse countdown. - if (twai_initiate_recovery() == ESP_OK) { - LOG_WARN("TWAIService", "Bus off detected. Initiating recovery..."); - return true; - } - break; - - case TWAI_STATE_STOPPED: - // Recovery finished! But we are still 'Stopped'. - // We need to re-start to get back to 'Running'. - if (twai_start() == ESP_OK) { - LOG_INFO("TWAIService", "Recovery complete. Bus is back online."); - return true; - } - break; - - case TWAI_STATE_RECOVERING: - // We are currently waiting for those 128 idle sequences. - // Just wait; don't trigger anything else. - break; - - case TWAI_STATE_RUNNING: - // Everything is fine. - break; - } - - return true; -} - -esp_err_t TWAIService::get_status(twai_status_info_t& status) { - esp_err_t res = twai_get_status_info(&status); - if (res != ESP_OK) { - // Log: Failed to get status info - LOG_ERR("TWAIService", "Failed to get TWAI status info: %d", res); - } - return res; -} - -#endif \ No newline at end of file diff --git a/lib/can/TWAI/twai_service.h b/lib/can/TWAI/twai_service.h deleted file mode 100644 index 75207d0..0000000 --- a/lib/can/TWAI/twai_service.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef TWAI_SERVICE_H -#define TWAI_SERVICE_H - -#if defined(ENV_ESP32) - -#include -#include -#include - -#include "service.h" - -using namespace CAN; - -struct TWAIConfig: public BaseServiceConfig { - const twai_general_config_t *general_config; - const twai_timing_config_t *timing_config; - const twai_filter_config_t *filter_config; -} - -class TWAIService : public Service { -public: - bool setup(const void * config) override; - bool tear_down() override; - bool send(Frame& frame) override; - const Frame* read() override; - bool recover() override; - - ~TWAIService() = default; -}; - -#endif // ENV_ESP32 - -#endif // TWAI_SERVICE_H \ No newline at end of file diff --git a/lib/can/dispatcher.h b/lib/can/dispatcher.h index 75f0ef8..777ccea 100644 --- a/lib/can/dispatcher.h +++ b/lib/can/dispatcher.h @@ -11,21 +11,90 @@ class watchdog; namespace CAN { +/** +* @brief Class for dispatching CAN frames to appropriate handlers. +*/ class Dispatcher { public: + /** + * @brief Returns the singleton instance of the dispatcher. + * @return instance: the singleton instance of the dispatcher. + */ static Dispatcher& get_instance(); + + /** + * @brief Deleted copy constructor to prevent copying of the singleton instance. + */ Dispatcher(const Dispatcher&) = delete; + + /** + * @brief Enqueues a CAN frame for dispatching. This function is typically called by the CAN service when a new frame is received. The frame will be added to the internal queue and processed in the order it was received. + * @param data: The CAN frame to enqueue. + */ Dispatcher& operator=(const Dispatcher&) = delete; + /** + * @brief Enqueues a CAN frame for dispatching. This function is typically called by the CAN service when a new frame is received. The frame will be added to the internal queue and processed in the order it was received. + * @param data: The CAN frame to enqueue. + */ void enqueue(const Frame& data); + + /** + * @brief Registers a route for a specific CAN ID. + * @param id: The CAN ID to register. + * @param handler: The handler to register for the ID. + */ void register_route(uint32_t id, IHandler* handler); + + /** + * @brief Dispatches frames from the queue to the appropriate handlers based on their CAN ID. This function should be called in a loop or a dedicated task to continuously process incoming frames. + */ void dispatch(); + + /** + * @brief Sets the queue for receiving frames to be dispatched. + * @param q: The queue to use for receiving frames. + */ void set_queue(Core::IQueue* q) { queue_rx = q; } + + /** + * @brief Sets the maximum number of routes that can be registered. + * @param max: The maximum number of routes. + */ + void set_max_routes(uint8_t max) { + max_routes = max; + delete[] routes; + routes = new IHandler*[max_routes](); // Initialize all to nullptr + } + + /** + * @brief Destructor for the dispatcher. + */ + ~Dispatcher() { + delete[] routes; + } private: - Dispatcher(); + /** + * @brief Constructor for the dispatcher. This is private to enforce the singleton pattern. + */ + Dispatcher() { + routes = new IHandler*[max_routes](); + } + + /** + * @brief The queue for receiving frames to be dispatched. + */ Core::IQueue* queue_rx; - // O(1) Lookup: Array of pointers to handlers (Size 2048 for 11-bit IDs) - IHandler* routes[2048]; + + /** + * @brief An array of pointers to handlers for each possible CAN ID. + */ + IHandler* routes; + + /** + * @brief The maximum number of routes that can be registered. + */ + uint8_t max_routes = 0; }; } // namespace CAN \ No newline at end of file diff --git a/lib/can/service.h b/lib/can/service.h index 2ed6c4b..db3ebe5 100644 --- a/lib/can/service.h +++ b/lib/can/service.h @@ -9,16 +9,51 @@ namespace CAN { +/** +* @brief Base class for CAN services. +*/ class Service { public: + /** + * @brief Sets up the CAN service. + * @param config: A pointer to the service configuration. + * @return success: True if the service was set up successfully, false otherwise. + */ virtual bool setup(const void * config) = 0; + + /** + * @brief Tears down the CAN service. + * @return success: True if the service was torn down successfully, false otherwise. + */ virtual bool tear_down() = 0; + + /** + * @brief Sends a CAN frame. + * @param frame: The frame to send. + * @return success: True if the frame was sent successfully, false otherwise. + */ virtual bool send(const Frame& frame) = 0; + + /** + * @brief Reads a CAN frame. + * @return pointer to the read frame, or nullptr if no frame is available. + */ virtual const Frame* read() = 0; + + /** + * @brief Attempts to recover the CAN service from an error state. + * @return success: True if recovery was successful, false otherwise. + */ virtual bool recover() = 0; + /** + * @brief Destructor for the CAN service. + */ ~Service() = default; private: + /** + * @brief Dispatcher for routing received frames to handlers. This is a reference to the singleton instance of the dispatcher, allowing the service to register handlers or dispatch received frames as needed. + */ Dispatcher& dispatcher = Dispatcher::get_instance(); }; diff --git a/lib/can/transmitter.h b/lib/can/transmitter.h index 5a0f379..7f3e913 100644 --- a/lib/can/transmitter.h +++ b/lib/can/transmitter.h @@ -9,20 +9,65 @@ namespace CAN { +/** +* @brief A class for transmitting CAN frames. +*/ class Transmitter { public: + /** + * @brief Returns the singleton instance of the transmitter. + * @return instance: the singleton instance of the transmitter. + */ static Transmitter& get_instance(); + + /** + * @brief Deleted copy constructor + */ Transmitter(const Transmitter&) = delete; + + /** + * @brief Deleted assignment operator + */ Transmitter& operator=(const Transmitter&) = delete; + /** + * @brief Sends a CAN frame. + * @param frame: The frame to send. + * @return success: True if the frame was sent successfully, false otherwise. + */ bool send(const Frame& frame); + + /** + * @brief Sets the service for transmitting frames. + * @param service: The service to use for transmission. + */ void set_service(Service* service); + + /** + * @brief Sets the queue for transmitting frames. + * @param queue: The queue to use for transmission. + */ void set_queue(Core::IQueue* queue); + + /** + * @brief Transmits frames from the queue using the service. This function should be called in a loop or a dedicated task to continuously transmit frames. + */ void transmit(); private: + /** + * @brief Constructor for the transmitter. + */ Transmitter(); + + /** + * @brief The queue for transmitting frames. + */ Core::IQueue* queue_tx; + + /** + * @brief The service for transmitting frames. + */ Service* service; }; diff --git a/lib/can/types.h b/lib/can/types.h index 85138df..bc8a744 100644 --- a/lib/can/types.h +++ b/lib/can/types.h @@ -6,15 +6,24 @@ namespace CAN { +/** +* @brief Enum for specifying the type of CAN service configuration. +*/ enum ServiceConfigType { TWAI }; +/** + * @brief Base class for CAN service configuration. + */ struct BaseServiceConfig { ServiceConfigType type; explicit BaseServiceConfig(ServiceConfigType t) : type(t) {} }; +/** + * @brief Structure for representing a CAN frame. + */ struct Frame { union { struct { @@ -32,8 +41,16 @@ struct Frame { uint8_t data_length_code; /**< Data length code */ uint8_t data[8]; /**< Data bytes (not relevant in RTR frame) */ + /* + ** @brief Default constructor for the CAN frame. + */ Frame() {} + /** + * @brief Constructor for the CAN frame. + * @param identifier: The frame identifier. + * @param message: A pointer to the message data. + */ template Frame(uint32_t identifier, T* message) { static_assert(sizeof(T) <= 8, "Message too large for CAN frame"); @@ -52,6 +69,11 @@ struct Frame { this->data[7] = msg_ptr[7]; } + /** + * @brief Constructor for the CAN frame. + * @param identifier: The frame identifier. + * @param data: A reference to the data array. + */ Frame(uint32_t identifier, uint8_t (&data)[8]) { this->data_length_code = 8; this->identifier = identifier; @@ -65,15 +87,30 @@ struct Frame { this->data[7] = data[7]; } + /** + * @brief Decodes the frame data into a specific type. + * @return pointer to the decoded data. + */ template T* decode() { return (T*) data; } }; +/** + * @brief Interface for handling CAN frames. + */ class IHandler { public: + /** + * @brief Handles a CAN frame. + * @param data: The frame to handle. + */ virtual void handle(const Frame& data) = 0; + + /** + * @brief Destructor for the CAN handler. + */ virtual ~IHandler() = default; }; diff --git a/lib/core/logger/i_logger.h b/lib/core/logger/i_logger.h index 785ca52..841ed42 100644 --- a/lib/core/logger/i_logger.h +++ b/lib/core/logger/i_logger.h @@ -10,6 +10,9 @@ namespace Core { */ class ILogger { public: + /** + * @brief Destructor for the logger backend. + */ virtual ~ILogger() = default; /** diff --git a/lib/core/logger/logger.h b/lib/core/logger/logger.h index 3cc81fa..21592e5 100644 --- a/lib/core/logger/logger.h +++ b/lib/core/logger/logger.h @@ -10,15 +10,28 @@ namespace Core { +// TODO: Use the flush method as necessary +/** +* @brief A simple logger class for handling log entries +*/ class Logger { public: - // This is the underlying function the Macro will call + /** + * @brief Logs a message with the specified level and metadata + * @param level: The log level + * @param file: The file name where the log is called + * @param line: The line number where the log is called + * @param tag: A tag for categorizing the log + * @param format: The format string for the log message + * @param ...: Variable arguments for the format string + */ static void log(LogLevel level, const char* file, int line, const char* tag, const char* format, ...) { + Logger instance = get_instance(); LogEntry entry; entry.level = level; entry.file = file; entry.line = line; - entry.timestamp = get_system_millis(); // Implement based on ESP32 or STM32 + entry.timestamp = instance.time_provider->get_system_millis(); // Implement based on ESP32 or STM32 strncpy(entry.tag, tag, sizeof(entry.tag)); @@ -28,41 +41,87 @@ class Logger { va_end(args); // Fire and forget: Push to the non-blocking queue - get_instance()._queue->enqueue(entry); + instance.queue->enqueue(entry); } + /** + * @brief Sets the output backend for the logger + * @param backend: The logger backend to use + */ static void set_output(ILogger* backend) { - get_instance()._backend = backend; + get_instance().backend = backend; } + /** + * @brief Sets the queue for the logger + * @param queue: The queue to use + */ static void set_queue(IQueue* queue) { - get_instance()._queue = queue; + get_instance().queue = queue; } - // Call this from a low-priority background task/thread + static void set_time_provider(TimeStampProvider* provider) { + get_instance().time_provider = provider; + } + + /** + * @brief Processes the log queue + * This should be called in a loop in the main thread to ensure logs are printed. + */ static void process() { + Logger instance = get_instance(); LogEntry entry; // Block until a log arrives - if (get_instance()._queue->dequeue(entry, 100)) { - if (get_instance()._backend) { + if (instance.queue->dequeue(entry, instance.max_timeout_ms)) { + if (instance.backend) { // The backend handles the actual string printing - get_instance()._backend->log(entry); + instance.backend->log(entry); } } } + /** + * @brief Sets the maximum timeout for log processing + * @param timeout_ms: The maximum timeout in milliseconds + */ + static void set_max_timeout(uint32_t timeout_ms) { + get_instance().max_timeout_ms = timeout_ms; + } + private: + /** + * @brief Constructor for the logger. + */ Logger(); + + /** + * @brief Returns the singleton instance of the logger. + * @return instance: the singleton instance of the logger. + */ static Logger& get_instance() { static Logger instance; return instance; } - IQueue* _queue; - ILogger* _backend = nullptr; - - // Platform specific: millis() for Arduino/ESP32 or HAL_GetTick() for STM32 - static uint32_t get_system_millis(); + /** + * @brief The queue for storing log entries. + */ + IQueue* queue; + + /** + * @brief The logger backend for outputting log entries. + */ + ILogger* backend = nullptr; + + /** + * @brief The time stamp provider for generating time stamps. + */ + TimeStampProvider* time_provider; + + /** + * @brief The maximum timeout for log processing. + */ + uint32_t max_timeout_ms = 100; }; } // namespace Core diff --git a/lib/core/logger/types.h b/lib/core/logger/types.h index e46a655..f3c2d23 100644 --- a/lib/core/logger/types.h +++ b/lib/core/logger/types.h @@ -5,6 +5,9 @@ namespace Core { +/** + * @brief Enumerate representing different log levels + */ enum class LogLevel { DEBUG, INFO, @@ -12,6 +15,9 @@ enum class LogLevel { CRITICAL }; +/** + * @brief Represents a single log entry + */ struct LogEntry { LogLevel level; uint32_t timestamp; // System millis diff --git a/lib/core/queue/i_queue.h b/lib/core/queue/i_queue.h index d5f2e00..9065823 100644 --- a/lib/core/queue/i_queue.h +++ b/lib/core/queue/i_queue.h @@ -9,6 +9,9 @@ namespace Core { template class IQueue { public: + /** + * @brief Destructor for the queue. + */ virtual ~IQueue() = default; /** @@ -25,7 +28,17 @@ class IQueue { * @return true if an item was retrieved, false if it timed out. */ virtual bool dequeue(T& data, uint32_t timeout_ms = 0) = 0; + + /** + * @brief Returns the number of items in the queue. + * @return size: the number of items in the queue. + */ virtual size_t size() const = 0; + + /** + * @brief Checks if the queue is full. + * @return true if the queue is full, false otherwise. + */ virtual bool is_full() const = 0; }; diff --git a/lib/core/task.h b/lib/core/task.h new file mode 100644 index 0000000..92ca122 --- /dev/null +++ b/lib/core/task.h @@ -0,0 +1,6 @@ +#ifndef CORE_TASK_H +#define CORE_TASK_H + +#include "task/controller.h" + +#endif // CORE_TASK_H \ No newline at end of file diff --git a/lib/core/task/controller.h b/lib/core/task/controller.h new file mode 100644 index 0000000..156effe --- /dev/null +++ b/lib/core/task/controller.h @@ -0,0 +1,71 @@ +#ifndef TASK_CONTROLLER_H +#define TASK_CONTROLLER_H + +#include +#include + +namespace Core { + + template +class TaskController { +public: + /** + * @brief Returns the singleton instance of the task controller. + * @return instance: the singleton instance of the task controller. + */ + TaskController get_instance() { + static TaskController instance; + return instance; + } + + /** + * @brief Creates a new task and adds it to the controller's management. The task is identified by an identifier of type T, which is used to manage the task (e.g., for deletion). The specifics of how the task is created (e.g., the function it runs, its priority, stack size) can be determined by additional parameters or by a predefined configuration within the controller. + * @param task: a function pointer or callable object that represents the task to be executed. This could be a lambda, a function pointer, or any callable that matches the expected signature for FreeRTOS tasks (e.g., void (*taskFunction)(void*)). + * @return identifier: string + */ + T create_task(U task); + + /** + * @brief Deletes a task from the controller's management. + * @param identifier: the identifier of the task to be deleted. + * @return success: true if the task was deleted, false otherwise. + */ + bool delete_task(T identifier); + + /** + * @brief Sets the maximum number of tasks that can be managed by the controller. + * @param limit: the maximum number of tasks. + */ + void set_max_tasks(size_t limit) { + delete[] task_identifiers; // Clean up existing identifiers if any + task_identifiers = new T[limit](); // Allocate new array for task identifiers + max_tasks = limit; + } + + /** + * @brief Destructor for the task controller. + */ + ~TaskController() { + delete[] task_identifiers; // Clean up allocated memory + } + +private: + TaskController() { + task_identifiers = new T[max_tasks](); + } + + // TODO: Make this a O(1) data structure + /** + * @brief Array to store task identifiers. + */ + T* task_identifiers; + + /** + * @brief The maximum number of tasks that can be managed. + */ + size_t max_tasks = 0; +}; + +} // namespace Core + +#endif // TASK_CONTROLLER_H \ No newline at end of file diff --git a/lib/core/thread/i_thread_strategy.h b/lib/core/thread/i_thread_strategy.h index 1374da9..36aac06 100644 --- a/lib/core/thread/i_thread_strategy.h +++ b/lib/core/thread/i_thread_strategy.h @@ -4,6 +4,8 @@ typedef void(*taskFunc)(void*); +#include + namespace Core { /** From f711b4bc6dd4fc20760974c5a6adcc50a2a58bf7 Mon Sep 17 00:00:00 2001 From: Porter McGary <56183563+mcgaryp@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:14:36 -0600 Subject: [PATCH 05/13] Refactor CAN and Logger modules: update dispatcher to use size_t for max_routes, add ITimeStampProvider interface, and enhance logger with timestamp provider integration --- lib/can/dispatcher.h | 13 ++++++++----- lib/core/logger.h | 1 + lib/core/logger/i_timestamp_provider.h | 12 ++++++++++++ lib/core/logger/logger.h | 8 +++++--- lib/core/queue.h | 1 + lib/core/queue/i_queue.h | 2 +- lib/core/task/controller.h | 4 ++-- 7 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 lib/core/logger/i_timestamp_provider.h diff --git a/lib/can/dispatcher.h b/lib/can/dispatcher.h index 777ccea..49ca6ef 100644 --- a/lib/can/dispatcher.h +++ b/lib/can/dispatcher.h @@ -1,4 +1,5 @@ -#pragma once +#ifndef CAN_DISPATCHER_H +#define CAN_DISPATCHER_H #include #include @@ -61,7 +62,7 @@ class Dispatcher { * @brief Sets the maximum number of routes that can be registered. * @param max: The maximum number of routes. */ - void set_max_routes(uint8_t max) { + void set_max_routes(size_t max) { max_routes = max; delete[] routes; routes = new IHandler*[max_routes](); // Initialize all to nullptr @@ -89,12 +90,14 @@ class Dispatcher { /** * @brief An array of pointers to handlers for each possible CAN ID. */ - IHandler* routes; + IHandler** routes; /** * @brief The maximum number of routes that can be registered. */ - uint8_t max_routes = 0; + size_t max_routes = 0; }; -} // namespace CAN \ No newline at end of file +} // namespace CAN + +#endif // CAN_DISPATCHER_H \ No newline at end of file diff --git a/lib/core/logger.h b/lib/core/logger.h index 88bbdf5..5793d8f 100644 --- a/lib/core/logger.h +++ b/lib/core/logger.h @@ -2,6 +2,7 @@ #define CORE_LOGGER_UMBRELLA_H #include "logger/i_logger.h" +#include "logger/i_timestamp_provider.h" #include "logger/types.h" #include "logger/logger.h" diff --git a/lib/core/logger/i_timestamp_provider.h b/lib/core/logger/i_timestamp_provider.h new file mode 100644 index 0000000..9364225 --- /dev/null +++ b/lib/core/logger/i_timestamp_provider.h @@ -0,0 +1,12 @@ +#ifndef I_TIME_STAMP_PROVIDER_H +#define I_TIME_STAMP_PROVIDER_H + +#include + +class ITimeStampProvider { +public: + virtual ~ITimeStampProvider() = default; + virtual uint64_t get_timestamp() = 0; +}; + +#endif // I_TIME_STAMP_PROVIDER_H \ No newline at end of file diff --git a/lib/core/logger/logger.h b/lib/core/logger/logger.h index 21592e5..1572ff6 100644 --- a/lib/core/logger/logger.h +++ b/lib/core/logger/logger.h @@ -7,6 +7,8 @@ #include #include "i_logger.h" +#include "i_timestamp_provider.h" +#include "types.h" namespace Core { @@ -31,7 +33,7 @@ class Logger { entry.level = level; entry.file = file; entry.line = line; - entry.timestamp = instance.time_provider->get_system_millis(); // Implement based on ESP32 or STM32 + entry.timestamp = instance.time_provider->get_timestamp(); // Implement based on ESP32 or STM32 strncpy(entry.tag, tag, sizeof(entry.tag)); @@ -60,7 +62,7 @@ class Logger { get_instance().queue = queue; } - static void set_time_provider(TimeStampProvider* provider) { + static void set_time_provider(ITimeStampProvider* provider) { get_instance().time_provider = provider; } @@ -116,7 +118,7 @@ class Logger { /** * @brief The time stamp provider for generating time stamps. */ - TimeStampProvider* time_provider; + ITimeStampProvider* time_provider; /** * @brief The maximum timeout for log processing. diff --git a/lib/core/queue.h b/lib/core/queue.h index a009724..f272086 100644 --- a/lib/core/queue.h +++ b/lib/core/queue.h @@ -1,3 +1,4 @@ +// This is an umbrella header for the queue module. #ifndef QUEUE_H #define QUEUE_H diff --git a/lib/core/queue/i_queue.h b/lib/core/queue/i_queue.h index 9065823..ceec3ef 100644 --- a/lib/core/queue/i_queue.h +++ b/lib/core/queue/i_queue.h @@ -19,7 +19,7 @@ class IQueue { * @param data The item to copy into the queue. * @return true if successful, false if the queue is full. */ - virtual void enqueue(const T& data) = 0; + virtual bool enqueue(const T& data) = 0; /** * @brief Removes an item from the queue. diff --git a/lib/core/task/controller.h b/lib/core/task/controller.h index 156effe..92549c8 100644 --- a/lib/core/task/controller.h +++ b/lib/core/task/controller.h @@ -23,14 +23,14 @@ class TaskController { * @param task: a function pointer or callable object that represents the task to be executed. This could be a lambda, a function pointer, or any callable that matches the expected signature for FreeRTOS tasks (e.g., void (*taskFunction)(void*)). * @return identifier: string */ - T create_task(U task); + virtual T create_task(U task) = 0; /** * @brief Deletes a task from the controller's management. * @param identifier: the identifier of the task to be deleted. * @return success: true if the task was deleted, false otherwise. */ - bool delete_task(T identifier); + virtual bool delete_task(T identifier) = 0; /** * @brief Sets the maximum number of tasks that can be managed by the controller. From 581cb061685d864411a612a54ada4f66e0315f49 Mon Sep 17 00:00:00 2001 From: Hibyehello <36666883+Hibyehello@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:59:00 -0600 Subject: [PATCH 06/13] Fixes to allow tests to pass with new infrastructure Move the core api into include for use with the hardware libraries --- {lib/battery => include}/battery.h | 2 +- {lib/can => include}/can.h | 8 +-- {lib/core => include}/core.h | 8 +-- {lib => include}/core/lock.h | 0 {lib => include}/core/lock/i_lock_strategy.h | 0 {lib => include}/core/lock/lock_guard.h | 0 {lib => include}/core/logger.h | 0 {lib => include}/core/logger/i_logger.h | 0 .../core/logger/i_timestamp_provider.h | 0 {lib => include}/core/logger/logger.h | 4 +- {lib => include}/core/logger/types.h | 0 {lib => include}/core/queue.h | 0 {lib => include}/core/queue/i_queue.h | 4 +- {lib => include}/core/task.h | 0 {lib => include}/core/task/controller.h | 0 {lib => include}/core/thread.h | 0 .../core/thread/i_thread_strategy.h | 0 lib/can/dispatcher.h | 4 +- lib/can/service.h | 13 +++-- lib/can/transmitter.cpp | 8 +-- lib/can/transmitter.h | 10 ++-- lib/can/types.h | 3 + lib/inverter/DTIX50/heartbeat.cpp | 9 ++- lib/inverter/DTIX50/heartbeat.h | 8 +-- platformio.ini | 2 +- test/mocks/can/mock_can_service.cpp | 33 ----------- test/mocks/can/mock_can_service.h | 57 +++++++++++++++---- test/mocks/mocks.h | 1 + test/mocks/strategies/native_queue_strategy.h | 43 ++++++++++++++ test/test_battery/test_battery_messages.cpp | 4 +- .../test_inverter_heartbeat_controller.cpp | 31 +++++++--- 31 files changed, 161 insertions(+), 91 deletions(-) rename {lib/battery => include}/battery.h (81%) rename {lib/can => include}/can.h (52%) rename {lib/core => include}/core.h (63%) rename {lib => include}/core/lock.h (100%) rename {lib => include}/core/lock/i_lock_strategy.h (100%) rename {lib => include}/core/lock/lock_guard.h (100%) rename {lib => include}/core/logger.h (100%) rename {lib => include}/core/logger/i_logger.h (100%) rename {lib => include}/core/logger/i_timestamp_provider.h (100%) rename {lib => include}/core/logger/logger.h (99%) rename {lib => include}/core/logger/types.h (100%) rename {lib => include}/core/queue.h (100%) rename {lib => include}/core/queue/i_queue.h (91%) rename {lib => include}/core/task.h (100%) rename {lib => include}/core/task/controller.h (100%) rename {lib => include}/core/thread.h (100%) rename {lib => include}/core/thread/i_thread_strategy.h (100%) delete mode 100644 test/mocks/can/mock_can_service.cpp create mode 100644 test/mocks/strategies/native_queue_strategy.h diff --git a/lib/battery/battery.h b/include/battery.h similarity index 81% rename from lib/battery/battery.h rename to include/battery.h index 3abba6e..cf0e7af 100644 --- a/lib/battery/battery.h +++ b/include/battery.h @@ -2,6 +2,6 @@ #ifndef BATTERY_H #define BATTERY_H -#include "messages.h" +#include "../../lib/battery/messages.h" #endif // BATTERY_H \ No newline at end of file diff --git a/lib/can/can.h b/include/can.h similarity index 52% rename from lib/can/can.h rename to include/can.h index 06ecc28..1d345e3 100644 --- a/lib/can/can.h +++ b/include/can.h @@ -2,9 +2,9 @@ #ifndef CAN_H #define CAN_H -#include "dispatcher.h" -#include "service.h" -#include "transmitter.h" -#include "types.h" +#include "../../lib/can/dispatcher.h" +#include "../../lib/can/service.h" +#include "../../lib/can/transmitter.h" +#include "../../lib/can/types.h" #endif // CAN_H \ No newline at end of file diff --git a/lib/core/core.h b/include/core.h similarity index 63% rename from lib/core/core.h rename to include/core.h index 6ec9b0b..14c1d3b 100644 --- a/lib/core/core.h +++ b/include/core.h @@ -2,9 +2,9 @@ #ifndef CORE_H #define CORE_H -#include "lock.h" -#include "thread.h" -#include "queue.h" -#include "logger.h" +#include "core/lock.h" +#include "core/thread.h" +#include "core/queue.h" +#include "core/logger.h" #endif // CORE_H \ No newline at end of file diff --git a/lib/core/lock.h b/include/core/lock.h similarity index 100% rename from lib/core/lock.h rename to include/core/lock.h diff --git a/lib/core/lock/i_lock_strategy.h b/include/core/lock/i_lock_strategy.h similarity index 100% rename from lib/core/lock/i_lock_strategy.h rename to include/core/lock/i_lock_strategy.h diff --git a/lib/core/lock/lock_guard.h b/include/core/lock/lock_guard.h similarity index 100% rename from lib/core/lock/lock_guard.h rename to include/core/lock/lock_guard.h diff --git a/lib/core/logger.h b/include/core/logger.h similarity index 100% rename from lib/core/logger.h rename to include/core/logger.h diff --git a/lib/core/logger/i_logger.h b/include/core/logger/i_logger.h similarity index 100% rename from lib/core/logger/i_logger.h rename to include/core/logger/i_logger.h diff --git a/lib/core/logger/i_timestamp_provider.h b/include/core/logger/i_timestamp_provider.h similarity index 100% rename from lib/core/logger/i_timestamp_provider.h rename to include/core/logger/i_timestamp_provider.h diff --git a/lib/core/logger/logger.h b/include/core/logger/logger.h similarity index 99% rename from lib/core/logger/logger.h rename to include/core/logger/logger.h index 1572ff6..9847e87 100644 --- a/lib/core/logger/logger.h +++ b/include/core/logger/logger.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include "i_logger.h" #include "i_timestamp_provider.h" @@ -94,7 +94,7 @@ class Logger { /** * @brief Constructor for the logger. */ - Logger(); + Logger() {}; /** * @brief Returns the singleton instance of the logger. diff --git a/lib/core/logger/types.h b/include/core/logger/types.h similarity index 100% rename from lib/core/logger/types.h rename to include/core/logger/types.h diff --git a/lib/core/queue.h b/include/core/queue.h similarity index 100% rename from lib/core/queue.h rename to include/core/queue.h diff --git a/lib/core/queue/i_queue.h b/include/core/queue/i_queue.h similarity index 91% rename from lib/core/queue/i_queue.h rename to include/core/queue/i_queue.h index ceec3ef..59cce31 100644 --- a/lib/core/queue/i_queue.h +++ b/include/core/queue/i_queue.h @@ -27,7 +27,7 @@ class IQueue { * @param timeout_ms How long to wait if the queue is empty (0 for non-blocking). * @return true if an item was retrieved, false if it timed out. */ - virtual bool dequeue(T& data, uint32_t timeout_ms = 0) = 0; + virtual bool dequeue(T& outData, uint32_t timeout_ms = 0) = 0; /** * @brief Returns the number of items in the queue. @@ -39,7 +39,7 @@ class IQueue { * @brief Checks if the queue is full. * @return true if the queue is full, false otherwise. */ - virtual bool is_full() const = 0; + virtual bool is_full() = 0; }; } // namespace Core diff --git a/lib/core/task.h b/include/core/task.h similarity index 100% rename from lib/core/task.h rename to include/core/task.h diff --git a/lib/core/task/controller.h b/include/core/task/controller.h similarity index 100% rename from lib/core/task/controller.h rename to include/core/task/controller.h diff --git a/lib/core/thread.h b/include/core/thread.h similarity index 100% rename from lib/core/thread.h rename to include/core/thread.h diff --git a/lib/core/thread/i_thread_strategy.h b/include/core/thread/i_thread_strategy.h similarity index 100% rename from lib/core/thread/i_thread_strategy.h rename to include/core/thread/i_thread_strategy.h diff --git a/lib/can/dispatcher.h b/lib/can/dispatcher.h index 49ca6ef..e064348 100644 --- a/lib/can/dispatcher.h +++ b/lib/can/dispatcher.h @@ -3,8 +3,8 @@ #include #include -#include -#include +#include +#include #include "types.h" diff --git a/lib/can/service.h b/lib/can/service.h index db3ebe5..cf914b6 100644 --- a/lib/can/service.h +++ b/lib/can/service.h @@ -26,6 +26,10 @@ class Service { * @return success: True if the service was torn down successfully, false otherwise. */ virtual bool tear_down() = 0; + + virtual void stop_listening() = 0; + + virtual void start_listening() = 0; /** * @brief Sends a CAN frame. @@ -35,8 +39,9 @@ class Service { virtual bool send(const Frame& frame) = 0; /** - * @brief Reads a CAN frame. + * @brief Reads a CAN frame and passes it to the dispatch callback * @return pointer to the read frame, or nullptr if no frame is available. + * TODO: Figure out if we actually need a return here */ virtual const Frame* read() = 0; @@ -49,12 +54,12 @@ class Service { /** * @brief Destructor for the CAN service. */ - ~Service() = default; + virtual ~Service() = default; private: /** - * @brief Dispatcher for routing received frames to handlers. This is a reference to the singleton instance of the dispatcher, allowing the service to register handlers or dispatch received frames as needed. + * @brief Callback that is called from read to pass the incoming data out */ - Dispatcher& dispatcher = Dispatcher::get_instance(); + void (*dispatch)(void* data); }; diff --git a/lib/can/transmitter.cpp b/lib/can/transmitter.cpp index 98706ed..2f3a353 100644 --- a/lib/can/transmitter.cpp +++ b/lib/can/transmitter.cpp @@ -2,9 +2,9 @@ namespace CAN { -Transmitter& Transmitter::get_instance() { +Transmitter* Transmitter::get_instance() { static Transmitter instance; - return instance; + return &instance; } void Transmitter::set_service(Service* s) { @@ -22,7 +22,7 @@ bool Transmitter::send(const Frame& frame) { return false; } - return service->send(frame); + return queue_tx->enqueue(frame); } void Transmitter::transmit() { @@ -36,7 +36,7 @@ void Transmitter::transmit() { // Service or queue not set, cannot process LOG_ERR("Transmitter", "Service not set, cannot transmit frames"); return; - } + } Frame frame; if (queue_tx->dequeue(frame)) { diff --git a/lib/can/transmitter.h b/lib/can/transmitter.h index 7f3e913..d8c6f5c 100644 --- a/lib/can/transmitter.h +++ b/lib/can/transmitter.h @@ -1,8 +1,8 @@ #ifndef CAN_TRANSMITTER_H #define CAN_TRANSMITTER_H -#include -#include +#include +#include #include "service.h" #include "types.h" @@ -18,7 +18,7 @@ class Transmitter { * @brief Returns the singleton instance of the transmitter. * @return instance: the singleton instance of the transmitter. */ - static Transmitter& get_instance(); + static Transmitter* get_instance(); /** * @brief Deleted copy constructor @@ -31,7 +31,7 @@ class Transmitter { Transmitter& operator=(const Transmitter&) = delete; /** - * @brief Sends a CAN frame. + * @brief Non-blocking. Adds Can Frame to the transmit queue. * @param frame: The frame to send. * @return success: True if the frame was sent successfully, false otherwise. */ @@ -58,7 +58,7 @@ class Transmitter { /** * @brief Constructor for the transmitter. */ - Transmitter(); + Transmitter() {} /** * @brief The queue for transmitting frames. diff --git a/lib/can/types.h b/lib/can/types.h index bc8a744..62c728e 100644 --- a/lib/can/types.h +++ b/lib/can/types.h @@ -24,6 +24,9 @@ struct BaseServiceConfig { /** * @brief Structure for representing a CAN frame. */ + +// TODO: Make sure that this actually lines up with a standard frame, link below has good details +// https://www.hms-networks.com/tech-blog/blogpost/hms-blog/2024/06/18/can-message-format-an-overview struct Frame { union { struct { diff --git a/lib/inverter/DTIX50/heartbeat.cpp b/lib/inverter/DTIX50/heartbeat.cpp index 99c26f9..0db5418 100644 --- a/lib/inverter/DTIX50/heartbeat.cpp +++ b/lib/inverter/DTIX50/heartbeat.cpp @@ -5,8 +5,8 @@ using namespace CAN; namespace Inverter { namespace DTIX50 { -Heartbeat::Heartbeat(std::shared_ptr canProvider, std::unique_ptr lock_strategy, std::unique_ptr thread_strategy) { - m_canProvider = canProvider; +Heartbeat::Heartbeat(Transmitter* canTransmitter, std::unique_ptr lock_strategy, std::unique_ptr thread_strategy) { + m_canTransmitter = canTransmitter; m_shouldStop_mut = std::move(lock_strategy); m_thread = std::move(thread_strategy); @@ -50,14 +50,13 @@ void Heartbeat::heartbeat(void* s) { for(;;) { // Check if it's time to stop - // TODO: Write a timeout self->m_shouldStop_mut->lock(); if(self->m_shouldStop) { self->m_shouldStop_mut->unlock(); // Send drive disable Frame frame(0x0C52, &self->disable); - self->m_canProvider->transmit(frame, 1000); + self->m_canTransmitter->send(frame); return; } self->m_shouldStop_mut->unlock(); @@ -65,7 +64,7 @@ void Heartbeat::heartbeat(void* s) { // Send drive enable Frame frame(0x0C52, &self->enable); - self->m_canProvider->transmit(frame, 1000); + self->m_canTransmitter->send(frame); self->m_thread->sleep(250U); } diff --git a/lib/inverter/DTIX50/heartbeat.h b/lib/inverter/DTIX50/heartbeat.h index cb4dc6b..19cd5dd 100644 --- a/lib/inverter/DTIX50/heartbeat.h +++ b/lib/inverter/DTIX50/heartbeat.h @@ -3,8 +3,8 @@ #include -#include "core/core.h" -#include "can/can.h" +#include +#include #include "commands.h" #include "messages.h" @@ -19,12 +19,12 @@ class Heartbeat { bool m_shouldStop; std::unique_ptr m_shouldStop_mut; std::unique_ptr m_thread; - std::shared_ptr m_canProvider; + Transmitter* m_canTransmitter; Command::SetDriveEnable enable; Command::SetDriveEnable disable; public: - Heartbeat(std::shared_ptr canProvider, std::unique_ptr lock_strategy, std::unique_ptr thread_strategy); + Heartbeat(Transmitter* canTransmitter, std::unique_ptr lock_strategy, std::unique_ptr thread_strategy); void start(); void stop(); diff --git a/platformio.ini b/platformio.ini index f850157..647fd56 100644 --- a/platformio.ini +++ b/platformio.ini @@ -6,7 +6,7 @@ build_flags = -std=c++11 -UUNITY_INCLUDE_CONFIG_H -I .pio/libdeps/native/Unity/src - -I lib + -I include -I test/mocks lib_deps = throwtheswitch/Unity@^2.5.2 diff --git a/test/mocks/can/mock_can_service.cpp b/test/mocks/can/mock_can_service.cpp deleted file mode 100644 index b9eadbc..0000000 --- a/test/mocks/can/mock_can_service.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "mock_can_service.h" - -namespace MOCKS { - -bool MockCanService::setup(const void * config) { - return true; -} - -bool MockCanService::tear_down() { - return true; -} - -void MockCanService::stop_listening() { - -} - -void MockCanService::start_listening() { - -} - -bool MockCanService::send(Frame* frame) { - return true; -} - -const Frame* MockCanService::read() { - return nullptr; -} - -bool MockCanService::recover() { - return true; -} - -} // namespace MOCKS \ No newline at end of file diff --git a/test/mocks/can/mock_can_service.h b/test/mocks/can/mock_can_service.h index cb5e01b..29ff0de 100644 --- a/test/mocks/can/mock_can_service.h +++ b/test/mocks/can/mock_can_service.h @@ -1,24 +1,59 @@ #ifndef MOCK_CAN_SERVICE_H #define MOCK_CAN_SERVICE_H +#include + #include using namespace CAN; -using namespace std; namespace MOCKS { -class MockCanService : public Service { +class MockCanService : public CAN::Service { public: - bool setup(const void * config) = 0; - bool tear_down() = 0; - void stop_listening() = 0; - void start_listening() = 0; - bool send(Frame* frame) = 0; - const Frame* read() = 0; - bool recover() = 0; - - ~Service() = default; + std::function on_send = [](const Frame&){ return true; }; + + struct CallCount { + int setup = 0; + int tear_down = 0; + int stop_listening = 0; + int start_listening = 0; + int send = 0; + int read = 0; + int recover = 0; + } calls; + + bool setup(const void* config) override { + calls.setup++; + return true; + } + + bool tear_down() override { + calls.tear_down++; + return true; + } + + void stop_listening() override { + calls.stop_listening++; + } + void start_listening() override { + calls.start_listening++; + } + bool send(const Frame& frame) override { + calls.send++; + return on_send(frame); + } + const Frame* read() override { + calls.read++; + return nullptr; + } + bool recover() override { + calls.recover++; + return true; + } + + MockCanService() = default; + ~MockCanService() = default; }; } // namespace MOCKS diff --git a/test/mocks/mocks.h b/test/mocks/mocks.h index a02f78e..642ae6c 100644 --- a/test/mocks/mocks.h +++ b/test/mocks/mocks.h @@ -4,6 +4,7 @@ #include "can/mock_can_service.h" #include "strategies/native_lock_strategy.h" +#include "strategies/native_queue_strategy.h" #include "strategies/native_thread_strategy.h" #endif // MOCKS_H \ No newline at end of file diff --git a/test/mocks/strategies/native_queue_strategy.h b/test/mocks/strategies/native_queue_strategy.h new file mode 100644 index 0000000..8191b68 --- /dev/null +++ b/test/mocks/strategies/native_queue_strategy.h @@ -0,0 +1,43 @@ +#ifndef NATIVE_QUEUE_STRATEGY_H +#define NATIVE_QUEUE_STRATEGY_H + +#include + +#include + +namespace MOCKS { +template +class NativeQueueStrategy : public Core::IQueue { +public: + bool enqueue(const T& data) override { + m_queue.push(data); + + return true; + } + + bool dequeue(T& outData, uint32_t timeout_ms) override + { + if(m_queue.empty()) { + return false; + } + + outData = m_queue.front(); + m_queue.pop(); + return true; + } + + size_t size() const override { + return 255; // Just to define the function + } + + bool is_full() override { + return false; + } + +private: + std::queue m_queue; +}; + +} // namespace MOCKS + +#endif \ No newline at end of file diff --git a/test/test_battery/test_battery_messages.cpp b/test/test_battery/test_battery_messages.cpp index b443640..a60ae72 100644 --- a/test/test_battery/test_battery_messages.cpp +++ b/test/test_battery/test_battery_messages.cpp @@ -1,6 +1,6 @@ #include "test_main.h" -#include -#include +#include +#include #include using namespace CAN; diff --git a/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp b/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp index f8b8dc3..f13c923 100644 --- a/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp +++ b/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp @@ -10,33 +10,50 @@ using namespace Inverter; using namespace MOCKS; +Transmitter* canTransmitter = Transmitter::get_instance(); + +void transmit_loop(void *) { + for(int i = 0; i < 1000; i++) { + canTransmitter->transmit(); + // A Slight delay to replicate some real time delay + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + void test_Heartbeat() { MockCanService* canService = new MockCanService(); // Most likely the ownership should be outside of the class int drive_type[2] = {0, 0}; - canService->on_transmit = [&drive_type](const Frame* frame, Tick tick){ - if(frame->data[0] == 1) + canService->on_send = [&drive_type](const Frame& frame){ + if(frame.data[0] == 1) drive_type[0]++; - else if (frame->data[0] == 0) + else if (frame.data[0] == 0) drive_type[1]++; - return Result::OK; + return true; }; - std::shared_ptr canProvider(new Provider((Service*)canService, PIN::NUM_12, PIN::NUM_14)); + + NativeQueueStrategy* queue = new NativeQueueStrategy(); + + canTransmitter->set_service(canService); + canTransmitter->set_queue(queue); std::unique_ptr lockStrategy(new NativeLockStrategy()); // We'll want the class to recieve ownership std::unique_ptr threadStrategy(new NativeThreadStrategy()); // We'll want the class to recieve ownership - DTIX50::Heartbeat heartbeat(canProvider, std::move(lockStrategy), std::move(threadStrategy)); + DTIX50::Heartbeat heartbeat(canTransmitter, std::move(lockStrategy), std::move(threadStrategy)); + std::thread transmit_thread = std::thread(transmit_loop, nullptr); + TEST_ASSERT(!heartbeat.started()); heartbeat.start(); TEST_ASSERT(heartbeat.started()); // We should always get at least 3 transmits std::this_thread::sleep_for(std::chrono::seconds(1)); - TEST_ASSERT_GREATER_OR_EQUAL(3, ((MockCanService*)canService)->calls.transmit); + TEST_ASSERT_GREATER_OR_EQUAL(3, ((MockCanService*)canService)->calls.send); TEST_ASSERT_GREATER_OR_EQUAL(2, drive_type[0]); // Verify that we have sent at least 2 drive enables. heartbeat.stop(); + transmit_thread.join(); TEST_ASSERT(!heartbeat.started()); From aec6d6a71f4d79d462566093033a87b4c40fd9b6 Mon Sep 17 00:00:00 2001 From: Hibyehello <36666883+Hibyehello@users.noreply.github.com> Date: Wed, 27 May 2026 09:08:51 -0600 Subject: [PATCH 07/13] Clean up and fixing building --- README.md | 18 ++++++++++++------ include/battery.h | 6 +++--- include/can.h | 10 ---------- include/core/queue/i_queue.h | 2 +- include/core/task/controller.h | 4 ++-- include/core_can.h | 10 ++++++++++ {lib => include}/inverter/DTIX50.h | 8 ++++---- lib/can/service.h | 11 +++++++---- lib/can/transmitter.cpp | 3 +++ lib/can/types.h | 2 -- lib/inverter/DTIX50/heartbeat.h | 2 +- platformio.ini | 8 +++++--- test/mocks/can/mock_can_service.h | 5 ++++- test/mocks/strategies/native_queue_strategy.h | 2 +- test/test_battery/test_battery_messages.cpp | 2 +- test/test_can/test_frame.cpp | 2 +- .../DTIX50/test_inverter_command.cpp | 6 +++--- .../test_inverter_heartbeat_controller.cpp | 14 +++++++------- .../DTIX50/test_inverter_message.cpp | 6 +++--- test/test_inverter/test_main.cpp | 2 +- 20 files changed, 69 insertions(+), 54 deletions(-) delete mode 100644 include/can.h create mode 100644 include/core_can.h rename {lib => include}/inverter/DTIX50.h (51%) diff --git a/README.md b/README.md index dc95366..7bc3219 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ -# ESP32 Race Car Core Library +# BYUI Formula-Hybrid Race Car Core Library [![CI Pipeline](https://github.com/byui-formula-hybrid/Core/actions/workflows/ci.yml/badge.svg)](https://github.com/byui-formula-hybrid/Core/actions/workflows/ci.yml) -This is a PlatformIO-based ESP32 project for a race car with multiple modular components. Each component is implemented as a separate library for clean organization and easy testing. +This is a PlatformIO-based project for a race car with multiple modular components. Each component is implemented as a separate library for clean organization and easy testing. This library is designed to be microcontroller agnostic, which means that we design the general hardware layout and implement hardware agnostic logic. ## Project Structure + ``` Core/ ├── .github/ # GitHub Actions workflows and templates @@ -44,6 +48,7 @@ Core/ **New to the project? Start here:** + #### For Linux/macOS ```bash @@ -57,7 +62,7 @@ cd Core # 3. Test that everything works ./.scripts/test.sh -# 4. Build for ESP32 +# 4. Build ./.scripts/build.sh ``` @@ -99,7 +104,7 @@ cd Core > # 3. Test that everything works > .\.scripts\powershell\test.ps1 > -> # 4. Build for ESP32 +> # 4. Build > .\.scripts\powershell\build.ps1 > ``` > @@ -120,8 +125,8 @@ cd Core > > # 3. Test that everything works > ./.scripts/test.sh -> -> # 4. Build for ESP32 +> +> # 4. Build > ./.scripts/build.sh > ``` > @@ -177,6 +182,7 @@ This project uses GitHub Actions for continuous integration with smart safeguard ## License + This project contains STM32CubeMX generated code © STMicroelectronics, licensed under ST’s software license - see [LICENSE](LICENSE_ST) file for details. All original source code and project documentation is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License - see the [LICENSE](LICENSE) file for details. diff --git a/include/battery.h b/include/battery.h index cf0e7af..7a8bad0 100644 --- a/include/battery.h +++ b/include/battery.h @@ -1,7 +1,7 @@ -// This is an umbrella header for the battery library. It includes all the necessary headers for using the battery library. +// This is an umbrella header for the battery library. It includes all the necessary headers for using the battery library. #ifndef BATTERY_H #define BATTERY_H -#include "../../lib/battery/messages.h" +#include "lib/battery/messages.h" -#endif // BATTERY_H \ No newline at end of file +#endif // BATTERY_H diff --git a/include/can.h b/include/can.h deleted file mode 100644 index 1d345e3..0000000 --- a/include/can.h +++ /dev/null @@ -1,10 +0,0 @@ -// This is the umbrella header for the CAN library. It includes all the necessary headers for using the CAN library. -#ifndef CAN_H -#define CAN_H - -#include "../../lib/can/dispatcher.h" -#include "../../lib/can/service.h" -#include "../../lib/can/transmitter.h" -#include "../../lib/can/types.h" - -#endif // CAN_H \ No newline at end of file diff --git a/include/core/queue/i_queue.h b/include/core/queue/i_queue.h index 59cce31..8182871 100644 --- a/include/core/queue/i_queue.h +++ b/include/core/queue/i_queue.h @@ -19,7 +19,7 @@ class IQueue { * @param data The item to copy into the queue. * @return true if successful, false if the queue is full. */ - virtual bool enqueue(const T& data) = 0; + virtual bool enqueue(const T data) = 0; /** * @brief Removes an item from the queue. diff --git a/include/core/task/controller.h b/include/core/task/controller.h index 92549c8..2009a5f 100644 --- a/include/core/task/controller.h +++ b/include/core/task/controller.h @@ -53,12 +53,12 @@ class TaskController { TaskController() { task_identifiers = new T[max_tasks](); } - +m // TODO: Make this a O(1) data structure /** * @brief Array to store task identifiers. */ - T* task_identifiers; + T** task_identifiers; /** * @brief The maximum number of tasks that can be managed. diff --git a/include/core_can.h b/include/core_can.h new file mode 100644 index 0000000..96c28f1 --- /dev/null +++ b/include/core_can.h @@ -0,0 +1,10 @@ +// This is the umbrella header for the CAN library. It includes all the necessary headers for using the CAN library. +#ifndef CAN_H +#define CAN_H + +#include "lib/can/dispatcher.h" +#include "lib/can/service.h" +#include "lib/can/transmitter.h" +#include "lib/can/types.h" + +#endif // CAN_H diff --git a/lib/inverter/DTIX50.h b/include/inverter/DTIX50.h similarity index 51% rename from lib/inverter/DTIX50.h rename to include/inverter/DTIX50.h index 7a64a79..513bb69 100644 --- a/lib/inverter/DTIX50.h +++ b/include/inverter/DTIX50.h @@ -2,8 +2,8 @@ #ifndef DTIX50_H #define DTIX50_H -#include "DTIX50/commands.h" -#include "DTIX50/heartbeat.h" -#include "DTIX50/messages.h" +#include "../../lib/inverter/DTIX50/commands.h" +#include "../../lib/inverter/DTIX50/heartbeat.h" +#include "../../lib/inverter/DTIX50/messages.h" -#endif // DTIX50_H \ No newline at end of file +#endif // DTIX50_H diff --git a/lib/can/service.h b/lib/can/service.h index cf914b6..04f6b1e 100644 --- a/lib/can/service.h +++ b/lib/can/service.h @@ -30,6 +30,12 @@ class Service { virtual void stop_listening() = 0; virtual void start_listening() = 0; + + /** + * @brief Checks if the hardware is able to take a frame to send + * @return success: True if the service can accept a frame to send + */ + virtual bool can_send() = 0; /** * @brief Sends a CAN frame. @@ -56,10 +62,7 @@ class Service { */ virtual ~Service() = default; private: - /** - * @brief Callback that is called from read to pass the incoming data out - */ - void (*dispatch)(void* data); + Dispatcher* m_dispatcher; }; diff --git a/lib/can/transmitter.cpp b/lib/can/transmitter.cpp index 2f3a353..0c90cb8 100644 --- a/lib/can/transmitter.cpp +++ b/lib/can/transmitter.cpp @@ -38,6 +38,9 @@ void Transmitter::transmit() { return; } + if(!service->can_send()) + return; + Frame frame; if (queue_tx->dequeue(frame)) { service->send(frame); diff --git a/lib/can/types.h b/lib/can/types.h index 62c728e..fc3a2bb 100644 --- a/lib/can/types.h +++ b/lib/can/types.h @@ -25,8 +25,6 @@ struct BaseServiceConfig { * @brief Structure for representing a CAN frame. */ -// TODO: Make sure that this actually lines up with a standard frame, link below has good details -// https://www.hms-networks.com/tech-blog/blogpost/hms-blog/2024/06/18/can-message-format-an-overview struct Frame { union { struct { diff --git a/lib/inverter/DTIX50/heartbeat.h b/lib/inverter/DTIX50/heartbeat.h index 19cd5dd..9aa2599 100644 --- a/lib/inverter/DTIX50/heartbeat.h +++ b/lib/inverter/DTIX50/heartbeat.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include "commands.h" #include "messages.h" diff --git a/platformio.ini b/platformio.ini index 647fd56..a695a74 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,12 +2,14 @@ platform = native test_framework = unity build_type = debug -build_flags = +build_flags = -std=c++11 -UUNITY_INCLUDE_CONFIG_H -I .pio/libdeps/native/Unity/src -I include -I test/mocks -lib_deps = + -I . +lib_deps = throwtheswitch/Unity@^2.5.2 -lib_ldf_mode = deep+ \ No newline at end of file + can +lib_ldf_mode = deep+ diff --git a/test/mocks/can/mock_can_service.h b/test/mocks/can/mock_can_service.h index 29ff0de..a058f29 100644 --- a/test/mocks/can/mock_can_service.h +++ b/test/mocks/can/mock_can_service.h @@ -3,7 +3,7 @@ #include -#include +#include using namespace CAN; @@ -39,6 +39,9 @@ class MockCanService : public CAN::Service { void start_listening() override { calls.start_listening++; } + bool can_send() { + return true; + } bool send(const Frame& frame) override { calls.send++; return on_send(frame); diff --git a/test/mocks/strategies/native_queue_strategy.h b/test/mocks/strategies/native_queue_strategy.h index 8191b68..9c1a4b5 100644 --- a/test/mocks/strategies/native_queue_strategy.h +++ b/test/mocks/strategies/native_queue_strategy.h @@ -9,7 +9,7 @@ namespace MOCKS { template class NativeQueueStrategy : public Core::IQueue { public: - bool enqueue(const T& data) override { + bool enqueue(const T data) override { m_queue.push(data); return true; diff --git a/test/test_battery/test_battery_messages.cpp b/test/test_battery/test_battery_messages.cpp index a60ae72..5294e57 100644 --- a/test/test_battery/test_battery_messages.cpp +++ b/test/test_battery/test_battery_messages.cpp @@ -1,6 +1,6 @@ #include "test_main.h" #include -#include +#include #include using namespace CAN; diff --git a/test/test_can/test_frame.cpp b/test/test_can/test_frame.cpp index 4c3dd40..871cf6b 100644 --- a/test/test_can/test_frame.cpp +++ b/test/test_can/test_frame.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include "test_main.h" diff --git a/test/test_inverter/DTIX50/test_inverter_command.cpp b/test/test_inverter/DTIX50/test_inverter_command.cpp index 0685f0d..e34359a 100644 --- a/test/test_inverter/DTIX50/test_inverter_command.cpp +++ b/test/test_inverter/DTIX50/test_inverter_command.cpp @@ -1,8 +1,8 @@ #include #include -#include -#include +#include +#include using namespace Inverter::Command; using namespace CAN; @@ -200,4 +200,4 @@ void run_DTIX50_command_tests() { RUN_TEST(test_SetMaxDCCurrent_command); RUN_TEST(test_SetMaxBrakeDCCurrent_command); RUN_TEST(test_SetDriveEnable_command); -} \ No newline at end of file +} diff --git a/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp b/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp index f13c923..6663229 100644 --- a/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp +++ b/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include using namespace Inverter; @@ -23,12 +23,12 @@ void transmit_loop(void *) { void test_Heartbeat() { MockCanService* canService = new MockCanService(); // Most likely the ownership should be outside of the class int drive_type[2] = {0, 0}; - canService->on_send = [&drive_type](const Frame& frame){ + canService->on_send = [&drive_type](const Frame& frame){ if(frame.data[0] == 1) - drive_type[0]++; + drive_type[0]++; else if (frame.data[0] == 0) drive_type[1]++; - return true; + return true; }; NativeQueueStrategy* queue = new NativeQueueStrategy(); @@ -39,7 +39,7 @@ void test_Heartbeat() { std::unique_ptr threadStrategy(new NativeThreadStrategy()); // We'll want the class to recieve ownership DTIX50::Heartbeat heartbeat(canTransmitter, std::move(lockStrategy), std::move(threadStrategy)); - + std::thread transmit_thread = std::thread(transmit_loop, nullptr); TEST_ASSERT(!heartbeat.started()); @@ -59,9 +59,9 @@ void test_Heartbeat() { TEST_ASSERT_EQUAL(1, drive_type[1]); // Verify that only one drive disable has been sent - free(canService); + delete canService; } void run_DTIX50_controller_tests() { RUN_TEST(test_Heartbeat); -} \ No newline at end of file +} diff --git a/test/test_inverter/DTIX50/test_inverter_message.cpp b/test/test_inverter/DTIX50/test_inverter_message.cpp index f61f9b1..f447008 100644 --- a/test/test_inverter/DTIX50/test_inverter_message.cpp +++ b/test/test_inverter/DTIX50/test_inverter_message.cpp @@ -1,8 +1,8 @@ #include #include -#include -#include +#include +#include using namespace Inverter; using namespace CAN; @@ -207,4 +207,4 @@ void run_DTIX50_message_tests() { RUN_TEST(test_message24_encode_decode); RUN_TEST(test_message25_encode_decode); RUN_TEST(test_message26_encode_decode); -} \ No newline at end of file +} diff --git a/test/test_inverter/test_main.cpp b/test/test_inverter/test_main.cpp index 96f002f..87c5dd7 100644 --- a/test/test_inverter/test_main.cpp +++ b/test/test_inverter/test_main.cpp @@ -17,4 +17,4 @@ int main() { run_DTIX50_command_tests(); run_DTIX50_controller_tests(); return UNITY_END(); -} \ No newline at end of file +} From 23ef78a0cb6f960d3fbe93bcb5f5c7bb9617f3d1 Mon Sep 17 00:00:00 2001 From: Hibyehello <36666883+Hibyehello@users.noreply.github.com> Date: Wed, 27 May 2026 09:57:04 -0600 Subject: [PATCH 08/13] Minor changes for build TODO: Fix TaskController --- include/battery.h | 3 ++- include/core/task/controller.h | 2 +- include/core_can.h | 9 +++++---- lib/can/service.h | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/battery.h b/include/battery.h index 7a8bad0..8793545 100644 --- a/include/battery.h +++ b/include/battery.h @@ -2,6 +2,7 @@ #ifndef BATTERY_H #define BATTERY_H -#include "lib/battery/messages.h" +// NOTE: Due to the inclusion of multiple lib folders in hardware, these need to be relative +#include "../lib/battery/messages.h" #endif // BATTERY_H diff --git a/include/core/task/controller.h b/include/core/task/controller.h index 2009a5f..2075d3c 100644 --- a/include/core/task/controller.h +++ b/include/core/task/controller.h @@ -53,7 +53,7 @@ class TaskController { TaskController() { task_identifiers = new T[max_tasks](); } -m + // TODO: Make this a O(1) data structure /** * @brief Array to store task identifiers. diff --git a/include/core_can.h b/include/core_can.h index 96c28f1..f5fa43a 100644 --- a/include/core_can.h +++ b/include/core_can.h @@ -2,9 +2,10 @@ #ifndef CAN_H #define CAN_H -#include "lib/can/dispatcher.h" -#include "lib/can/service.h" -#include "lib/can/transmitter.h" -#include "lib/can/types.h" +// NOTE: Due to the inclusion of multiple lib folders in hardware, these need to be relative +#include "../lib/can/dispatcher.h" +#include "../lib/can/service.h" +#include "../lib/can/transmitter.h" +#include "../lib/can/types.h" #endif // CAN_H diff --git a/lib/can/service.h b/lib/can/service.h index 04f6b1e..83b25fe 100644 --- a/lib/can/service.h +++ b/lib/can/service.h @@ -61,7 +61,7 @@ class Service { * @brief Destructor for the CAN service. */ virtual ~Service() = default; -private: +protected: Dispatcher* m_dispatcher; }; From fba54ba37d057998b6de9df62fe3b49de67494a7 Mon Sep 17 00:00:00 2001 From: Hibyehello <36666883+Hibyehello@users.noreply.github.com> Date: Wed, 27 May 2026 15:16:16 -0600 Subject: [PATCH 09/13] Make some TaskControllers members protected instead of private --- include/core/task/controller.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/include/core/task/controller.h b/include/core/task/controller.h index 2075d3c..e8ce029 100644 --- a/include/core/task/controller.h +++ b/include/core/task/controller.h @@ -49,11 +49,7 @@ class TaskController { delete[] task_identifiers; // Clean up allocated memory } -private: - TaskController() { - task_identifiers = new T[max_tasks](); - } - +protected: // TODO: Make this a O(1) data structure /** * @brief Array to store task identifiers. @@ -64,6 +60,11 @@ class TaskController { * @brief The maximum number of tasks that can be managed. */ size_t max_tasks = 0; + +private: + TaskController() { + task_identifiers = new T[max_tasks](); + } }; } // namespace Core From 7b93ba5d0327c22960de88304c5110a3a4efb0ad Mon Sep 17 00:00:00 2001 From: Hibyehello <36666883+Hibyehello@users.noreply.github.com> Date: Sat, 6 Jun 2026 15:40:36 -0600 Subject: [PATCH 10/13] Added a non-virtual task controller that uses thread strategies Also added many minor changes for actual implementation --- include/core/task.h | 2 +- include/core/thread/i_thread_strategy.h | 21 +++++++- lib/can/dispatcher.cpp | 24 +++++---- lib/can/dispatcher.h | 2 +- lib/can/transmitter.cpp | 13 ++--- lib/can/transmitter.h | 3 +- lib/core/task/controller.cpp | 48 ++++++++++++++++++ {include => lib}/core/task/controller.h | 49 ++++++++++++------- lib/inverter/DTIX50/heartbeat.cpp | 30 +++++++----- lib/inverter/DTIX50/heartbeat.h | 9 ++-- test/mocks/can/mock_can_service.h | 2 +- .../mocks/strategies/native_thread_strategy.h | 2 +- .../test_inverter_heartbeat_controller.cpp | 7 ++- 13 files changed, 151 insertions(+), 61 deletions(-) create mode 100644 lib/core/task/controller.cpp rename {include => lib}/core/task/controller.h (62%) diff --git a/include/core/task.h b/include/core/task.h index 92ca122..54faf1e 100644 --- a/include/core/task.h +++ b/include/core/task.h @@ -1,6 +1,6 @@ #ifndef CORE_TASK_H #define CORE_TASK_H -#include "task/controller.h" +#include "../../lib/core/task/controller.h" #endif // CORE_TASK_H \ No newline at end of file diff --git a/include/core/thread/i_thread_strategy.h b/include/core/thread/i_thread_strategy.h index 36aac06..263a4d9 100644 --- a/include/core/thread/i_thread_strategy.h +++ b/include/core/thread/i_thread_strategy.h @@ -18,11 +18,30 @@ namespace Core { */ class iThreadStrategy { public: + iThreadStrategy() = default; virtual ~iThreadStrategy() = default; virtual void setup(const char* name = nullptr, const uint32_t priority = 0, const uint32_t attributes = 0) = 0; virtual uint32_t create(taskFunc task, void* argument) = 0; - virtual void join() = 0; + virtual void kill() = 0; virtual void sleep(const uint32_t millis) = 0; + + /** + * @brief Set the thread Handle + * @param handle: the thread handle + */ + void SetHandle(uint32_t handle) { + m_handle = handle; + } + + /** + * @brief Get the thread Handle + * @return the thread handle + */ + uint32_t GetHandle() { + return m_handle; + } +protected: + uint32_t m_handle; }; } diff --git a/lib/can/dispatcher.cpp b/lib/can/dispatcher.cpp index 8e7632a..7b0d94a 100644 --- a/lib/can/dispatcher.cpp +++ b/lib/can/dispatcher.cpp @@ -17,16 +17,20 @@ void Dispatcher::enqueue(const Frame& data) { queue_rx->enqueue(data); } -void Dispatcher::dispatch() { - if (queue_rx == nullptr) { - // Queue not set, cannot dispatch - LOG_ERR("Dispatcher", "Queue not set, cannot dispatch frames"); - return; - } - - Frame data; - if (queue_rx->dequeue(data) && data.identifier < 2048 && routes[data.identifier] != nullptr) { - routes[data.identifier]->handle(data); +void Dispatcher::dispatch(void* data) { + Dispatcher* self = (Dispatcher*)data; + while(true) { + if (self->queue_rx == nullptr) { + // Queue not set, cannot dispatch + LOG_ERR("Dispatcher", "Queue not set, cannot dispatch frames"); + return; + } + + Frame data; + if (self->queue_rx->dequeue(data)) { + if(data.identifier < 2048 && self->routes[data.identifier] != nullptr) + self->routes[data.identifier]->handle(data); + } } } diff --git a/lib/can/dispatcher.h b/lib/can/dispatcher.h index e064348..a479982 100644 --- a/lib/can/dispatcher.h +++ b/lib/can/dispatcher.h @@ -50,7 +50,7 @@ class Dispatcher { /** * @brief Dispatches frames from the queue to the appropriate handlers based on their CAN ID. This function should be called in a loop or a dedicated task to continuously process incoming frames. */ - void dispatch(); + static void dispatch(void* data); /** * @brief Sets the queue for receiving frames to be dispatched. diff --git a/lib/can/transmitter.cpp b/lib/can/transmitter.cpp index 0c90cb8..beaef4f 100644 --- a/lib/can/transmitter.cpp +++ b/lib/can/transmitter.cpp @@ -25,25 +25,26 @@ bool Transmitter::send(const Frame& frame) { return queue_tx->enqueue(frame); } -void Transmitter::transmit() { - if (queue_tx == nullptr) { +void Transmitter::transmit(void* data) { + Transmitter* self = (Transmitter*)data; + if (self->queue_tx == nullptr) { // Service or queue not set, cannot process LOG_ERR("Transmitter", "Queue not set, cannot transmit frames"); return; } - if (service == nullptr) { + if (self->service == nullptr) { // Service or queue not set, cannot process LOG_ERR("Transmitter", "Service not set, cannot transmit frames"); return; } - if(!service->can_send()) + if(!self->service->can_send()) return; Frame frame; - if (queue_tx->dequeue(frame)) { - service->send(frame); + if (self->queue_tx->dequeue(frame)) { + self->service->send(frame); } }; diff --git a/lib/can/transmitter.h b/lib/can/transmitter.h index d8c6f5c..b97e324 100644 --- a/lib/can/transmitter.h +++ b/lib/can/transmitter.h @@ -51,8 +51,9 @@ class Transmitter { /** * @brief Transmits frames from the queue using the service. This function should be called in a loop or a dedicated task to continuously transmit frames. + * @param data: Intended use is to pass the singleton instance into the function and to match the task callback signature */ - void transmit(); + static void transmit(void* data); private: /** diff --git a/lib/core/task/controller.cpp b/lib/core/task/controller.cpp new file mode 100644 index 0000000..9a1edc1 --- /dev/null +++ b/lib/core/task/controller.cpp @@ -0,0 +1,48 @@ +#include "controller.h" + +namespace Core { + +TaskController* TaskController::get_instance() { + static TaskController instance; + return &instance; +} + +void TaskController::setup_task(const char* name, int priority, int attrs) { + setup_info.name = name; + setup_info.priority = priority; + setup_info.attrs = attrs; + + task_setup = true; +} + +int TaskController::create_task(iThreadStrategy* thread, taskFunc task, void* arg) { + if(num_of_tasks++ > max_tasks) return -1; + if(!task_setup) return -1; + task_setup = false; + + thread->setup(setup_info.name, setup_info.priority, setup_info.attrs); + + uint32_t id = thread->create(task, arg); + + task_identifiers->emplace(id, thread); + + return id; +} + +bool TaskController::delete_task(int id) { + task_identifiers->at(id)->kill(); + + delete task_identifiers->at(id); + task_identifiers->erase(id); + num_of_tasks--; + + return true; +} + + + +void TaskController::set_max_tasks(size_t limit) { + max_tasks = limit; +} + +} \ No newline at end of file diff --git a/include/core/task/controller.h b/lib/core/task/controller.h similarity index 62% rename from include/core/task/controller.h rename to lib/core/task/controller.h index e8ce029..aa93732 100644 --- a/include/core/task/controller.h +++ b/lib/core/task/controller.h @@ -3,50 +3,53 @@ #include #include +#include + +#include namespace Core { - template class TaskController { public: /** * @brief Returns the singleton instance of the task controller. * @return instance: the singleton instance of the task controller. */ - TaskController get_instance() { - static TaskController instance; - return instance; - } + static TaskController* get_instance(); + + /** + * @brief Setups up the task before creating the thread + * @param name: The task name + * @param priority: The task priority + * @param attrs: The task attributes + */ + void setup_task(const char* name, int priority, int attrs); /** * @brief Creates a new task and adds it to the controller's management. The task is identified by an identifier of type T, which is used to manage the task (e.g., for deletion). The specifics of how the task is created (e.g., the function it runs, its priority, stack size) can be determined by additional parameters or by a predefined configuration within the controller. * @param task: a function pointer or callable object that represents the task to be executed. This could be a lambda, a function pointer, or any callable that matches the expected signature for FreeRTOS tasks (e.g., void (*taskFunction)(void*)). - * @return identifier: string + * @return the identifier of the task */ - virtual T create_task(U task) = 0; + int create_task(iThreadStrategy* thread, taskFunc task, void* arg); /** * @brief Deletes a task from the controller's management. - * @param identifier: the identifier of the task to be deleted. + * @param idx: the index of the task to delete * @return success: true if the task was deleted, false otherwise. */ - virtual bool delete_task(T identifier) = 0; + bool delete_task(int id); /** * @brief Sets the maximum number of tasks that can be managed by the controller. * @param limit: the maximum number of tasks. */ - void set_max_tasks(size_t limit) { - delete[] task_identifiers; // Clean up existing identifiers if any - task_identifiers = new T[limit](); // Allocate new array for task identifiers - max_tasks = limit; - } + void set_max_tasks(size_t limit); /** * @brief Destructor for the task controller. */ ~TaskController() { - delete[] task_identifiers; // Clean up allocated memory + //delete[] task_identifiers; // Clean up allocated memory } protected: @@ -54,16 +57,24 @@ class TaskController { /** * @brief Array to store task identifiers. */ - T** task_identifiers; + std::map* task_identifiers; /** * @brief The maximum number of tasks that can be managed. */ - size_t max_tasks = 0; + size_t max_tasks = 3; + size_t num_of_tasks = 0; + + bool task_setup = false; + + struct { + const char* name; + int priority; + int attrs; + } setup_info; -private: TaskController() { - task_identifiers = new T[max_tasks](); + task_identifiers = new std::map(); } }; diff --git a/lib/inverter/DTIX50/heartbeat.cpp b/lib/inverter/DTIX50/heartbeat.cpp index 0db5418..96a1b9a 100644 --- a/lib/inverter/DTIX50/heartbeat.cpp +++ b/lib/inverter/DTIX50/heartbeat.cpp @@ -5,29 +5,32 @@ using namespace CAN; namespace Inverter { namespace DTIX50 { -Heartbeat::Heartbeat(Transmitter* canTransmitter, std::unique_ptr lock_strategy, std::unique_ptr thread_strategy) { +Heartbeat::Heartbeat(Transmitter* canTransmitter, std::unique_ptr lock_strategy) { m_canTransmitter = canTransmitter; m_shouldStop_mut = std::move(lock_strategy); - m_thread = std::move(thread_strategy); + + m_taskController = Core::TaskController::get_instance(); + m_taskController->setup_task("inverter.DTIX50.heartbeat", // name + 0x17U, // priority - osPriorityBelowNormal7 + 0x01U // attributes - osThreadJoinable + ); m_shouldStop = false; m_started = false; enable = { 0x01, 0xFFFFFFFFFFFFFF }; disable = { 0x00, 0xFFFFFFFFFFFFFF }; - - // TODO: Write an generic enum for thread attributes and priority - m_thread->setup("inverter.DTIX50.heartbeat", // name - 0x17U, // priority - osPriorityBelowNormal7 - 0x01U // attributes - osThreadJoinable - ); } -void Heartbeat::start() { +void Heartbeat::start(Core::iThreadStrategy* thread_strategy) { if(m_started) return; + m_thread_strategy = thread_strategy; + // Start the heartbeat for drive enable - m_thread->create(Heartbeat::heartbeat, this); + uint32_t handle = m_taskController->create_task(thread_strategy, Heartbeat::heartbeat, this); + + m_thread_strategy->SetHandle(handle); m_started = true; } @@ -39,7 +42,7 @@ void Heartbeat::stop() { m_shouldStop_mut->unlock(); // Wait for the heartbeat to actually stop before sending drive disable - m_thread->join(); + m_taskController->delete_task(m_thread_strategy->GetHandle()); m_started = false; } @@ -47,7 +50,8 @@ void Heartbeat::stop() { // Sends a drive enable every ~250 milliseconds so the car doesn't stop void Heartbeat::heartbeat(void* s) { Heartbeat* self = (Heartbeat*)s; - for(;;) { + + while(true) { // Check if it's time to stop self->m_shouldStop_mut->lock(); @@ -66,7 +70,7 @@ void Heartbeat::heartbeat(void* s) { self->m_canTransmitter->send(frame); - self->m_thread->sleep(250U); + self->m_thread_strategy->sleep(250U); } } diff --git a/lib/inverter/DTIX50/heartbeat.h b/lib/inverter/DTIX50/heartbeat.h index 9aa2599..49b8d0f 100644 --- a/lib/inverter/DTIX50/heartbeat.h +++ b/lib/inverter/DTIX50/heartbeat.h @@ -5,6 +5,7 @@ #include #include +#include #include "commands.h" #include "messages.h" @@ -18,15 +19,17 @@ class Heartbeat { bool m_started; bool m_shouldStop; std::unique_ptr m_shouldStop_mut; - std::unique_ptr m_thread; + Core::TaskController* m_taskController; + Core::iThreadStrategy* m_thread_strategy; + uint32_t m_thread; // A thread handle Transmitter* m_canTransmitter; Command::SetDriveEnable enable; Command::SetDriveEnable disable; public: - Heartbeat(Transmitter* canTransmitter, std::unique_ptr lock_strategy, std::unique_ptr thread_strategy); + Heartbeat(Transmitter* canTransmitter, std::unique_ptr lock_strategy); - void start(); + void start(Core::iThreadStrategy* thread_strategy); void stop(); bool started() { return m_started; } diff --git a/test/mocks/can/mock_can_service.h b/test/mocks/can/mock_can_service.h index a058f29..df23603 100644 --- a/test/mocks/can/mock_can_service.h +++ b/test/mocks/can/mock_can_service.h @@ -39,7 +39,7 @@ class MockCanService : public CAN::Service { void start_listening() override { calls.start_listening++; } - bool can_send() { + bool can_send() override { return true; } bool send(const Frame& frame) override { diff --git a/test/mocks/strategies/native_thread_strategy.h b/test/mocks/strategies/native_thread_strategy.h index 97c7e6b..52ec79f 100644 --- a/test/mocks/strategies/native_thread_strategy.h +++ b/test/mocks/strategies/native_thread_strategy.h @@ -18,7 +18,7 @@ class NativeThreadStrategy : public Core::iThreadStrategy { return 0; } - void join() override { + void kill() override { if(m_thread.joinable()) m_thread.join(); } diff --git a/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp b/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp index 6663229..0b498ca 100644 --- a/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp +++ b/test/test_inverter/DTIX50/test_inverter_heartbeat_controller.cpp @@ -14,7 +14,7 @@ Transmitter* canTransmitter = Transmitter::get_instance(); void transmit_loop(void *) { for(int i = 0; i < 1000; i++) { - canTransmitter->transmit(); + Transmitter::transmit(canTransmitter); // A Slight delay to replicate some real time delay std::this_thread::sleep_for(std::chrono::milliseconds(1)); } @@ -36,14 +36,13 @@ void test_Heartbeat() { canTransmitter->set_service(canService); canTransmitter->set_queue(queue); std::unique_ptr lockStrategy(new NativeLockStrategy()); // We'll want the class to recieve ownership - std::unique_ptr threadStrategy(new NativeThreadStrategy()); // We'll want the class to recieve ownership - DTIX50::Heartbeat heartbeat(canTransmitter, std::move(lockStrategy), std::move(threadStrategy)); + DTIX50::Heartbeat heartbeat(canTransmitter, std::move(lockStrategy)); std::thread transmit_thread = std::thread(transmit_loop, nullptr); TEST_ASSERT(!heartbeat.started()); - heartbeat.start(); + heartbeat.start(new NativeThreadStrategy()); TEST_ASSERT(heartbeat.started()); // We should always get at least 3 transmits From 5ea4facc613f4bed7ad83db98dfd62b6551d94f5 Mon Sep 17 00:00:00 2001 From: Hibyehello <36666883+Hibyehello@users.noreply.github.com> Date: Sat, 6 Jun 2026 15:46:00 -0600 Subject: [PATCH 11/13] Make the dispatcher get_instance return a pointer --- lib/can/dispatcher.cpp | 4 ++-- lib/can/dispatcher.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/can/dispatcher.cpp b/lib/can/dispatcher.cpp index 7b0d94a..e5e6286 100644 --- a/lib/can/dispatcher.cpp +++ b/lib/can/dispatcher.cpp @@ -2,9 +2,9 @@ namespace CAN { -Dispatcher& Dispatcher::get_instance() { +Dispatcher* Dispatcher::get_instance() { static Dispatcher instance; - return instance; + return &instance; } void Dispatcher::enqueue(const Frame& data) { diff --git a/lib/can/dispatcher.h b/lib/can/dispatcher.h index a479982..beb69f5 100644 --- a/lib/can/dispatcher.h +++ b/lib/can/dispatcher.h @@ -21,7 +21,7 @@ class Dispatcher { * @brief Returns the singleton instance of the dispatcher. * @return instance: the singleton instance of the dispatcher. */ - static Dispatcher& get_instance(); + static Dispatcher* get_instance(); /** * @brief Deleted copy constructor to prevent copying of the singleton instance. From 6fb624d8d0e7a36734e4d4241dd2fc0c7a8b4c17 Mon Sep 17 00:00:00 2001 From: Hibyehello <36666883+Hibyehello@users.noreply.github.com> Date: Sat, 6 Jun 2026 21:23:15 -0600 Subject: [PATCH 12/13] Remove #define conflict --- include/core_can.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/core_can.h b/include/core_can.h index f5fa43a..d9fb810 100644 --- a/include/core_can.h +++ b/include/core_can.h @@ -1,6 +1,6 @@ // This is the umbrella header for the CAN library. It includes all the necessary headers for using the CAN library. -#ifndef CAN_H -#define CAN_H +#ifndef CORE_CAN_H +#define CORE_CAN_H // NOTE: Due to the inclusion of multiple lib folders in hardware, these need to be relative #include "../lib/can/dispatcher.h" From ea7f115ace71627d21621594cfecc70b9dac44f2 Mon Sep 17 00:00:00 2001 From: Hibyehello <36666883+Hibyehello@users.noreply.github.com> Date: Mon, 8 Jun 2026 09:09:28 -0600 Subject: [PATCH 13/13] Add a library.json file to aid in compiling as a library --- library.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 library.json diff --git a/library.json b/library.json new file mode 100644 index 0000000..2d12fb3 --- /dev/null +++ b/library.json @@ -0,0 +1,7 @@ +{ + "name": "Core", + "version": "0.1.0b", + "build": { + "srcDir": "." + } +} \ No newline at end of file