From fd96e01405be293213dc0b51b4a51f856f489141 Mon Sep 17 00:00:00 2001 From: iOsnaaente Date: Tue, 26 May 2026 16:24:54 -0300 Subject: [PATCH 1/4] wip: implementing a v1 fix to release --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a41eb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +venv/ +.venv/ \ No newline at end of file From a8f766e5284b09178806cb572492104a8520e462 Mon Sep 17 00:00:00 2001 From: iOsnaaente Date: Tue, 26 May 2026 17:21:44 -0300 Subject: [PATCH 2/4] wip: add seq_id and multi/fragmeted packet support --- .vscode/settings.json | 7 ++- include/serial_comm.h | 5 ++ include/serial_comm_messages.h | 90 ++++++++++++++++++++++++------- include/serial_comm_parser.h | 18 +++---- include/serial_comm_protocol.h | 98 +++++++++++++++------------------- src/serial_comm.cpp | 42 ++++++++++----- src/serial_comm_parser.cpp | 73 ++++++++++++++++--------- src/serial_comm_protocol.cpp | 40 +++++--------- 8 files changed, 221 insertions(+), 152 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 934189c..85c4f34 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,8 @@ { - "idf.currentSetup": "/home/iosnaaente/esp/v5.5.1/esp-idf" + "idf.currentSetup": "/home/iosnaaente/esp/v5.5.1/esp-idf", + + "editor.rulers": [ 75, 85 ], + "workbench.colorCustomizations": { + "editorRuler.foreground": "#174117" + }, } \ No newline at end of file diff --git a/include/serial_comm.h b/include/serial_comm.h index 6ba55ea..de73144 100644 --- a/include/serial_comm.h +++ b/include/serial_comm.h @@ -183,6 +183,9 @@ class SerialComm { private: /** * @brief Static RX callback from transport + * @param ctx User context (pointer to SerialComm instance) + * @param data Received data buffer + * @param len Length of received data */ static void transport_rx_callback( void* ctx, @@ -192,6 +195,8 @@ class SerialComm { /** * @brief Process incoming RX data + * @param data Received data buffer + * @param len Length of received data */ void process_rx_data( const uint8_t* data, size_t len ); diff --git a/include/serial_comm_messages.h b/include/serial_comm_messages.h index 7fdd541..2362509 100644 --- a/include/serial_comm_messages.h +++ b/include/serial_comm_messages.h @@ -1,5 +1,6 @@ /** - * @brief Serial communication protocol, structs, types and constants + * @file serial_comm_messages.h + * @brief Serial communication protocol, structs, types and constants */ #pragma once @@ -7,26 +8,77 @@ #include #include -constexpr uint8_t HEADER_0 = 0xAA; -constexpr uint8_t HEADER_1 = 0x55; -constexpr uint8_t HEADER_2 = 0xAA; - -constexpr uint8_t PROTOCOL_VERSION = 0x01; -constexpr size_t MAX_PAYLOAD_SIZE = 256; - -enum class Command : uint8_t { - READ_JOINTS = 0x01, - WRITE_JOINTS = 0x02, +/** + * @brief Serial communication protocol commands + * @note To add a new command, add it before the REPLY command and + * ensure the REPLY_MASK is set correctly + */ +enum class SerialCommCommand : uint8_t { + UNDEFINED = 0x00, + READ = 0x01, + WRITE = 0x02, PING = 0x03, READ_UTILITY = 0x04, WRITE_UTILITY = 0x05, - SMOOTH_MOVE = 0x06 }; -struct Packet { - uint8_t version; - Command command; - uint16_t payload_len; - uint8_t payload[MAX_PAYLOAD_SIZE]; - uint16_t crc; -}; + +/** + * @brief Serial communication protocol commands to string conversion + * @details Useful for logs/debugging. + * @param[in] cmd SerialCommCommand enum + * @return const char* SerialCommCommand string + */ +const char* serial_command_to_str(SerialCommCommand cmd){ + switch (cmd) { + case SerialCommCommand::UNDEFINED: + return "UNDEFINED"; + case SerialCommCommand::READ: + return "READ"; + case SerialCommCommand::WRITE: + return "WRITE"; + case SerialCommCommand::PING: + return "PING"; + case SerialCommCommand::READ_UTILITY: + return "READ_UTILITY"; + case SerialCommCommand::WRITE_UTILITY: + return "WRITE_UTILITY"; + case SerialCommCommand::SMOOTH_MOVE: + return "SMOOTH_MOVE"; + default: + return "UNKNOWN_COMMAND"; + } +} + +/** + * @brief Command flag bit mask + */ +#define SERIAL_COMM_CMD_REPLY_MASK 0x80 + + +/** + * @brief Commands Helpers + */ +static inline bool is_reply( SerialCommCommand cmd ) { + return ( static_cast(cmd) & SERIAL_COMM_CMD_REPLY_MASK ) != 0; +} + +static inline bool is_request( SerialCommCommand cmd ) { + return !is_reply(cmd); +} + + +static inline SerialCommCommand make_reply( SerialCommCommand cmd ) { + return static_cast( + static_cast(cmd) | + SERIAL_COMM_CMD_REPLY_MASK + ); +} + + +static inline SerialCommCommand make_request( SerialCommCommand cmd ) { + return static_cast( + static_cast(cmd) & + ~SERIAL_COMM_CMD_REPLY_MASK + ); +} \ No newline at end of file diff --git a/include/serial_comm_parser.h b/include/serial_comm_parser.h index 294a87c..63ea48f 100644 --- a/include/serial_comm_parser.h +++ b/include/serial_comm_parser.h @@ -34,6 +34,8 @@ class SerialCommParser { WAIT_HEADER_0 = 0, WAIT_HEADER_1, WAIT_HEADER_2, + GET_SEQ_ID_L, + GET_SEQ_ID_H, READ_VERSION, READ_COMMAND, READ_LENGTH_L, @@ -54,8 +56,6 @@ class SerialCommParser { uint16_t payload_index_; /** Temporary CRC low byte */ uint8_t crc_l_; - /** Parser synchronization state */ - bool synchronized_; public: /** @@ -64,9 +64,7 @@ class SerialCommParser { SerialCommParser() : state_( State::WAIT_HEADER_0 ), payload_index_(0), - crc_l_(0), - synchronized_(false) - { + crc_l_(0) { SerialCommProtocol::clear_packet( packet_ ); } @@ -92,13 +90,15 @@ class SerialCommParser { * @brief Parse a byte stream buffer * @param[in] data Stream buffer * @param[in] len Buffer length + * @param[out] consumed_bytes Number of bytes consumed from buffer * @param[out] out_packet Parsed packet * @return True Packet completed * @return False No valid packet found */ - bool parse_buffer( + bool parse_next_packet( const uint8_t* data, size_t len, + size_t &consumed_bytes, SerialCommProtoPacket& out_packet ); @@ -117,12 +117,6 @@ class SerialCommParser { */ State state() const; - /** - * @brief Check if parser is synchronized - * @return True Parser synchronized with stream - * @return False Parser waiting synchronization - */ - bool synchronized() const; /** * @brief Get current payload index diff --git a/include/serial_comm_protocol.h b/include/serial_comm_protocol.h index fe7f237..b468801 100644 --- a/include/serial_comm_protocol.h +++ b/include/serial_comm_protocol.h @@ -11,6 +11,7 @@ #pragma once +#include "serial_comm_messages.h" #include "serial_comm_utils.h" #include @@ -22,106 +23,101 @@ /** * @brief Protocol header bytes */ -#define SERIAL_COMM_HEADER_0 0xAA -#define SERIAL_COMM_HEADER_1 0x55 -#define SERIAL_COMM_HEADER_2 0xAA +constexpr uint8_t SERIAL_COMM_HEADER_0 = 0xAA; +constexpr uint8_t SERIAL_COMM_HEADER_1 = 0x55; +constexpr uint8_t SERIAL_COMM_HEADER_2 = 0xAA; + +/** + * @brief Protocol field sizes in bytes + */ +constexpr uint8_t SERIAL_COMM_HEADER_SIZE = 3; +constexpr uint8_t SERIAL_COMM_SEQ_SIZE = 2; +constexpr uint8_t SERIAL_COMM_PROTOCOL_VER_SIZE = 1; +constexpr uint8_t SERIAL_COMM_SRC_SIZE = 1; +constexpr uint8_t SERIAL_COMM_DST_SIZE = 1; +constexpr uint8_t SERIAL_COMM_FLAG_SIZE = 1; +constexpr uint8_t SERIAL_COMM_COMMAND_SIZE = 1; +constexpr uint8_t SERIAL_COMM_LENGTH_SIZE = 2; +constexpr uint8_t SERIAL_COMM_CRC_SIZE = 2; /** * @brief Current protocol version */ -#define SERIAL_COMM_PROTOCOL_VER1 0x01 -#define SERIAL_COMM_PROTOCOL_VER2 0x02 +constexpr uint8_t SERIAL_COMM_PROTOCOL_VER1 = 0x01; +constexpr uint8_t SERIAL_COMM_PROTOCOL_VER2 = 0x02; /** * @brief Maximum payload size */ -#define SERIAL_COMM_MAX_PAYLOAD_V1 512 -#define SERIAL_COMM_MAX_PAYLOAD_V2 1024 - -#define SERIAL_COMM_HEADER_SIZE 3 -#define SERIAL_COMM_PROTOCOL_VER_SIZE 1 -#define SERIAL_COMM_SRC_SIZE 1 -#define SERIAL_COMM_DST_SIZE 1 -#define SERIAL_COMM_SEQ_SIZE 2 -#define SERIAL_COMM_FLAG_SIZE 1 -#define SERIAL_COMM_COMMAND_SIZE 1 -#define SERIAL_COMM_LENGTH_SIZE 2 -#define SERIAL_COMM_CRC_SIZE 2 +constexpr size_t SERIAL_COMM_MAX_PAYLOAD_V1 = 512; +constexpr size_t SERIAL_COMM_MAX_PAYLOAD_V2 = 1024; /** * @brief Maximum packet size for V1 * HEADER(3) + * SEQ_ID(2) * VERSION(1) * COMMAND(1) * LENGTH(2) * PAYLOAD(1024) * CRC16(2) */ -#define SERIAL_COMM_MAX_PACKET_SIZE_V1 \ +constexpr size_t SERIAL_COMM_MAX_PACKET_SIZE_V1 = \ SERIAL_COMM_HEADER_SIZE + \ + SERIAL_COMM_SEQ_SIZE + \ SERIAL_COMM_PROTOCOL_VER_SIZE + \ SERIAL_COMM_COMMAND_SIZE + \ SERIAL_COMM_LENGTH_SIZE + \ SERIAL_COMM_MAX_PAYLOAD_V1 + \ - SERIAL_COMM_CRC_SIZE + SERIAL_COMM_CRC_SIZE; /** * @brief Maximum packet size for V2 * HEADER(3) + * SEQ_ID(2) * VERSION(1) * SRC(1) * DST(1) - * SEQ(2) * FLAG(1) * COMMAND(1) * LENGTH(2) * PAYLOAD(1024) * CRC16(2) */ -#define SERIAL_COMM_MAX_PACKET_SIZE_V2 \ +constexpr size_t SERIAL_COMM_MAX_PACKET_SIZE_V2 = \ SERIAL_COMM_HEADER_SIZE + \ + SERIAL_COMM_SEQ_SIZE + \ SERIAL_COMM_PROTOCOL_VER_SIZE + \ SERIAL_COMM_SRC_SIZE + \ SERIAL_COMM_DST_SIZE + \ - SERIAL_COMM_SEQ_SIZE + \ SERIAL_COMM_FLAG_SIZE + \ SERIAL_COMM_COMMAND_SIZE + \ SERIAL_COMM_LENGTH_SIZE + \ SERIAL_COMM_MAX_PAYLOAD_V2 + \ - SERIAL_COMM_CRC_SIZE - -/** - * @brief Protocol command identifiers - */ -enum class SerialCommProtoCommand : uint8_t { - UNKNOWN = 0x00, - PING = 0x01, - READ = 0x02, - WRITE = 0x03, - REPLY = 0x04, - ERROR = 0x05, - SYNC_WRITE = 0x06, - SYNC_READ = 0x07, - EVENT = 0x08, - ACK = 0x09, - NACK = 0x0A -}; + SERIAL_COMM_CRC_SIZE; /** * @brief Protocol packet structure * @param header Packet header containing metadata + * @param seq_id Packet sequence identifier: + * - For tracking and matching requests/replies * @param version Protocol version * @param command Command identifier * @param payload_len Packet payload length */ struct SerialCommProtoHeader { - uint8_t header[3]; - uint8_t version; - SerialCommProtoCommand command; - uint16_t payload_len; + uint8_t header[3] = { + SERIAL_COMM_HEADER_0, + SERIAL_COMM_HEADER_1, + SERIAL_COMM_HEADER_2 + }; + uint16_t seq_id = 0; + uint8_t version = SERIAL_COMM_PROTOCOL_VER1; + SerialCommCommand command = SerialCommCommand::UNDEFINED; + uint16_t payload_len = 0; }; @@ -134,7 +130,7 @@ struct SerialCommProtoHeader { */ struct SerialCommProtoPacket { SerialCommProtoHeader header; - uint8_t payload[SERIAL_COMM_MAX_PAYLOAD_V1 ]; + uint8_t payload[SERIAL_COMM_MAX_PAYLOAD_V2 ] = { 0 }; uint16_t crc = 0; }; @@ -221,14 +217,4 @@ class SerialCommProtocol { SerialCommProtoPacket& packet ); - - /** - * @brief Convert command enum to string - * @details Useful for logs/debugging. - * @param[in] cmd Command enum - * @return Command string - */ - static const char* command_to_str( - SerialCommProtoCommand cmd - ); -}; \ No newline at end of file +}; diff --git a/src/serial_comm.cpp b/src/serial_comm.cpp index 19f6757..f01c043 100644 --- a/src/serial_comm.cpp +++ b/src/serial_comm.cpp @@ -230,7 +230,8 @@ void SerialComm::transport_rx_callback( const uint8_t* data, size_t len ) { - if (ctx == nullptr){ + // Verify context + if ( ctx == nullptr ){ return; } auto* self = static_cast(ctx); @@ -239,6 +240,7 @@ void SerialComm::transport_rx_callback( void SerialComm::process_rx_data( const uint8_t* data, size_t len ) { + // Verify data pointer if (data == nullptr){ ESP_LOGE( TAG, "Received null data pointer" ); return; @@ -247,17 +249,33 @@ void SerialComm::process_rx_data( const uint8_t* data, size_t len ) { if ( this->interbyte_watchdog_ != nullptr ) { this->interbyte_watchdog_->kick(); } - // Parse Stream - SerialCommProtoPacket packet; - xSemaphoreTake( this->parser_mutex_, portMAX_DELAY ); - bool valid_packet = this->parser_.parse_buffer( data, len, packet ); - xSemaphoreGive( this->parser_mutex_ ); - - // Dispatch packet if valid - if ( valid_packet ) { - errCode err = this->dispatcher_.enqueue( packet ); - if ( err != errCode::OK ) { - ESP_LOGE( TAG, "Failed to enqueue packet" ); + // Parse Stream with multiples packets support + size_t offset_packet = 0; + while ( offset_packet < len ){ + SerialCommProtoPacket packet; + size_t consumed_bytes = 0; + xSemaphoreTake( this->parser_mutex_, portMAX_DELAY ); + bool valid_packet = this->parser_.parse_next_packet( + &data[offset_packet], + len - offset_packet, + consumed_bytes, + packet + ); + xSemaphoreGive( this->parser_mutex_ ); + + // Safety check to avoid infinite loop on parser error + if ( consumed_bytes == 0 ) { + ESP_LOGE( TAG, "Parser failed to consume bytes" ); + break; + } + offset_packet += consumed_bytes; + + // Dispatch packet if valid + if ( valid_packet ) { + errCode err = this->dispatcher_.enqueue( packet ); + if ( err != errCode::OK ) { + ESP_LOGE( TAG, "Failed to enqueue packet" ); + } } } } diff --git a/src/serial_comm_parser.cpp b/src/serial_comm_parser.cpp index 94ba011..2b58639 100644 --- a/src/serial_comm_parser.cpp +++ b/src/serial_comm_parser.cpp @@ -29,7 +29,7 @@ bool SerialCommParser::parse_byte( if (byte == SERIAL_COMM_HEADER_1) { this->state_ = State::WAIT_HEADER_2; } else { - reset(); + this->reset(); } break; } @@ -37,17 +37,34 @@ bool SerialCommParser::parse_byte( // Header 2 case State::WAIT_HEADER_2: { if (byte == SERIAL_COMM_HEADER_2) { - synchronized_ = true; - this->state_ = State::READ_VERSION; + this->state_ = State::GET_SEQ_ID_L; } else { - reset(); + this->reset(); } break; } + case State::GET_SEQ_ID_L: { + packet_.header.seq_id = byte; + this->state_ = State::GET_SEQ_ID_H; + break; + } + + case State::GET_SEQ_ID_H: { + packet_.header.seq_id |= (byte << 8); + this->state_ = State::READ_VERSION; + break; + } + // READ VERSION case State::READ_VERSION: { packet_.header.version = byte; + + if ( packet_.header.version != SERIAL_COMM_PROTOCOL_VER1 ) { + this->reset(); + break; + } + this->state_ = State::READ_COMMAND; break; } @@ -70,15 +87,22 @@ bool SerialCommParser::parse_byte( // READ LENGTH HIGH BYTE case State::READ_LENGTH_H: { packet_.header.payload_len |= (byte << 8); + // VALIDATE PAYLOAD SIZE - if ( packet_.header.payload_len > SERIAL_COMM_MAX_PAYLOAD_V1 ) { - reset(); + size_t max_payload_size = + packet_.header.version == SERIAL_COMM_PROTOCOL_VER1 ? + SERIAL_COMM_MAX_PAYLOAD_V1 : + SERIAL_COMM_MAX_PAYLOAD_V2; + if ( packet_.header.payload_len > max_payload_size ) { + this->reset(); break; } + // NO PAYLOAD if ( packet_.header.payload_len == 0 ) { this->state_ = State::READ_CRC_L; + } else { payload_index_ = 0; this->state_ = State::READ_PAYLOAD; @@ -111,23 +135,21 @@ bool SerialCommParser::parse_byte( if ( SerialCommProtocol::validate_packet(packet_)) { out_packet = packet_; this->state_ = State::PACKET_READY; - reset(); + this->reset(); return true; } - reset(); + this->reset(); break; } // PACKET READY case State::PACKET_READY: { - reset(); + this->reset(); break; } - // ERROR - case State::ERROR: default: { - reset(); + this->reset(); break; } } @@ -135,14 +157,20 @@ bool SerialCommParser::parse_byte( } -bool SerialCommParser::parse_buffer( +bool SerialCommParser::parse_next_packet( const uint8_t* data, size_t len, + size_t &consumed_bytes, SerialCommProtoPacket& out_packet ) { + // Verify input parameters if (data == nullptr) return false; + + // Inicialize output parameters + consumed_bytes = 0; for (size_t i = 0; i < len; i++) { + consumed_bytes++; if ( parse_byte( data[i], out_packet ) ) { return true; } @@ -153,10 +181,10 @@ bool SerialCommParser::parse_buffer( void SerialCommParser::reset() { this->state_ = State::WAIT_HEADER_0; - payload_index_ = 0; - crc_l_ = 0; - synchronized_ = false; - SerialCommProtocol::clear_packet( packet_ ); + this->packet_.header.payload_len = 0; + this->payload_index_ = 0; + this->packet_.crc = 0; + this->crc_l_ = 0; } @@ -165,18 +193,13 @@ SerialCommParser::State SerialCommParser::state() const { } -bool SerialCommParser::synchronized() const { - return synchronized_; -} - - size_t SerialCommParser::payload_index() const { - return payload_index_; + return this->payload_index_; } uint16_t SerialCommParser::expected_payload_size() const { - return packet_.header.payload_len; + return this->packet_.header.payload_len; } @@ -185,6 +208,8 @@ const char* SerialCommParser::state_to_str( State state ) { case State::WAIT_HEADER_0: return "WAIT_HEADER_0"; case State::WAIT_HEADER_1: return "WAIT_HEADER_1"; case State::WAIT_HEADER_2: return "WAIT_HEADER_2"; + case State::GET_SEQ_ID_L: return "GET_SEQ_ID_L"; + case State::GET_SEQ_ID_H: return "GET_SEQ_ID_H"; case State::READ_VERSION: return "READ_VERSION"; case State::READ_COMMAND: return "READ_COMMAND"; case State::READ_LENGTH_L: return "READ_LENGTH_L"; diff --git a/src/serial_comm_protocol.cpp b/src/serial_comm_protocol.cpp index 4419065..b2368bf 100644 --- a/src/serial_comm_protocol.cpp +++ b/src/serial_comm_protocol.cpp @@ -29,6 +29,10 @@ size_t SerialCommProtocol::encode( out_buffer[index++] = SERIAL_COMM_HEADER_1; out_buffer[index++] = SERIAL_COMM_HEADER_2; + // SEQ_ID + out_buffer[index++] = (packet.header.seq_id & 0xFF); + out_buffer[index++] = ((packet.header.seq_id >> 8) & 0xFF); + // VERSION out_buffer[index++] = packet.header.version; @@ -98,12 +102,18 @@ int32_t SerialCommProtocol::decode( } size_t index = 3; + // SEQ_ID + out_packet.header.seq_id = + raw_data[index] | + (raw_data[index + 1] << 8); + index += 2; + // VERSION out_packet.header.version = raw_data[index++]; // COMMAND out_packet.header.command = - static_cast( + static_cast( raw_data[index++] ); @@ -236,31 +246,5 @@ size_t SerialCommProtocol::packet_size( void SerialCommProtocol::clear_packet( SerialCommProtoPacket& packet ) { memset( &packet, 0, sizeof(SerialCommProtoPacket) ); packet.header.version = SERIAL_COMM_PROTOCOL_VER1; - packet.header.command = SerialCommProtoCommand::UNKNOWN; + packet.header.command = SerialCommCommand::UNKNOWN; } - - -const char* SerialCommProtocol::command_to_str( - SerialCommProtoCommand cmd -) { - switch (cmd) { - case SerialCommProtoCommand::PING: - return "PING"; - case SerialCommProtoCommand::READ: - return "READ"; - case SerialCommProtoCommand::WRITE: - return "WRITE"; - case SerialCommProtoCommand::REPLY: - return "REPLY"; - case SerialCommProtoCommand::ERROR: - return "ERROR"; - case SerialCommProtoCommand::EVENT: - return "EVENT"; - case SerialCommProtoCommand::ACK: - return "ACK"; - case SerialCommProtoCommand::NACK: - return "NACK"; - default: - return "UNKNOWN"; - } -} \ No newline at end of file From 49ccaea95ba8869d83099bbcb779d0d43e9f4b0e Mon Sep 17 00:00:00 2001 From: iOsnaaente Date: Tue, 26 May 2026 23:06:12 -0300 Subject: [PATCH 3/4] wip: developing middleware layer --- CMakeLists.txt | 37 ++ core/README.md | 33 ++ {src => core}/serial_comm.cpp | 6 +- {include => core}/serial_comm.h | 4 +- {include => core}/serial_comm_config.h | 0 {src => core}/serial_comm_dispatcher.cpp | 4 +- {include => core}/serial_comm_dispatcher.h | 4 +- {src => core}/serial_comm_parser.cpp | 2 +- {include => core}/serial_comm_parser.h | 0 {src => core}/serial_comm_protocol.cpp | 36 +- {include => core}/serial_comm_protocol.h | 0 {include => core}/serial_comm_transport.h | 0 {include => core}/serial_comm_utils.h | 0 core/serial_comm_watchdog.cpp | 162 ++++++ core/serial_comm_watchdog.h | 137 ++++++ include/serial_comm_eventloop.h | 32 -- include/serial_comm_events.h | 21 - include/serial_comm_watchdog.h | 211 -------- {include => messages}/serial_comm_messages.h | 2 - middleware/README.md | 17 + middleware/action/serial_comm_action.cpp | 103 ++++ middleware/action/serial_comm_action.h | 163 ++++++ middleware/action/serial_comm_action_base.h | 39 ++ middleware/serial_comm_manager.cpp | 301 ++++++++++++ middleware/serial_comm_manager.h | 465 ++++++++++++++++++ middleware/serial_comm_manager_action.cpp | 64 +++ middleware/serial_comm_manager_service.cpp | 159 ++++++ middleware/serial_comm_manager_topic.cpp | 124 +++++ middleware/serial_comm_serializer.h | 79 +++ .../serial_comm_transaction_manager.cpp | 152 ++++++ middleware/serial_comm_transaction_manager.h | 163 ++++++ middleware/service/serial_comm_service.cpp | 110 +++++ middleware/service/serial_comm_service.h | 149 ++++++ middleware/service/serial_comm_service_base.h | 40 ++ middleware/topic/serial_comm_topic.cpp | 76 +++ middleware/topic/serial_comm_topic.h | 127 +++++ middleware/topic/serial_comm_topic_base.h | 39 ++ ...eventloop.cpp => serial_comm_eventloop.cpp | 0 src/serial_comm_crc.cpp | 0 src/serial_comm_ringbuff.cpp | 0 transport/README.md | 25 + {src => transport}/uart_serial_comm.cpp | 0 {include => transport}/uart_serial_comm.h | 0 43 files changed, 2790 insertions(+), 296 deletions(-) create mode 100644 core/README.md rename {src => core}/serial_comm.cpp (98%) rename {include => core}/serial_comm.h (98%) rename {include => core}/serial_comm_config.h (100%) rename {src => core}/serial_comm_dispatcher.cpp (98%) rename {include => core}/serial_comm_dispatcher.h (98%) rename {src => core}/serial_comm_parser.cpp (99%) rename {include => core}/serial_comm_parser.h (100%) rename {src => core}/serial_comm_protocol.cpp (87%) rename {include => core}/serial_comm_protocol.h (100%) rename {include => core}/serial_comm_transport.h (100%) rename {include => core}/serial_comm_utils.h (100%) create mode 100644 core/serial_comm_watchdog.cpp create mode 100644 core/serial_comm_watchdog.h delete mode 100644 include/serial_comm_eventloop.h delete mode 100644 include/serial_comm_events.h delete mode 100644 include/serial_comm_watchdog.h rename {include => messages}/serial_comm_messages.h (96%) create mode 100644 middleware/README.md create mode 100644 middleware/action/serial_comm_action.cpp create mode 100644 middleware/action/serial_comm_action.h create mode 100644 middleware/action/serial_comm_action_base.h create mode 100644 middleware/serial_comm_manager.cpp create mode 100644 middleware/serial_comm_manager.h create mode 100644 middleware/serial_comm_manager_action.cpp create mode 100644 middleware/serial_comm_manager_service.cpp create mode 100644 middleware/serial_comm_manager_topic.cpp create mode 100644 middleware/serial_comm_serializer.h create mode 100644 middleware/serial_comm_transaction_manager.cpp create mode 100644 middleware/serial_comm_transaction_manager.h create mode 100644 middleware/service/serial_comm_service.cpp create mode 100644 middleware/service/serial_comm_service.h create mode 100644 middleware/service/serial_comm_service_base.h create mode 100644 middleware/topic/serial_comm_topic.cpp create mode 100644 middleware/topic/serial_comm_topic.h create mode 100644 middleware/topic/serial_comm_topic_base.h rename src/serial_comm_eventloop.cpp => serial_comm_eventloop.cpp (100%) delete mode 100644 src/serial_comm_crc.cpp delete mode 100644 src/serial_comm_ringbuff.cpp create mode 100644 transport/README.md rename {src => transport}/uart_serial_comm.cpp (100%) rename {include => transport}/uart_serial_comm.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index e69de29..a65249f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +idf_component_register( + SRCS + "serial_comm_eventloop.cpp" + + "core/serial_comm_dispatcher.cpp" + "core/serial_comm_protocol.cpp" + "core/serial_comm_watchdog.cpp" + "core/serial_comm_parser.cpp" + "core/serial_comm.cpp" + + "middleware/serial_comm_transaction_manager.cpp" + "middleware/serial_comm_manager_service.cpp" + "middleware/serial_comm_manager_action.cpp" + "middleware/serial_comm_manager_topic.cpp" + "middleware/serial_comm_manager.cpp" + + "middleware/service/serial_comm_service.cpp" + "middleware/action/serial_comm_action.cpp" + "middleware/topic/serial_comm_topic.cpp" + + "transport/uart_serial_comm.cpp" + + INCLUDE_DIRS + "." + "core" + "messages" + "middleware" + "transport" + + REQUIRES + freertos + esp_timer + driver + log +) + +target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_17) \ No newline at end of file diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..3e143e7 --- /dev/null +++ b/core/README.md @@ -0,0 +1,33 @@ +# Protocol Layer + +## Protocol infrastructure layer + +Responsável por: + +- parser; +- framing; +- CRC; +- seq_id; +- dispatcher; +- watchdog; +- serialization raw; +- transport orchestration. + +Não conhece: + +- services; +- topics; +- actions; +- payloads da aplicação. +- middleware/ +- Semantic communication layer + +Responsável por: + +- services; +- topics; +- actions; +- transactions; +- typed callbacks; +- serializers; +- request/reply abstraction. diff --git a/src/serial_comm.cpp b/core/serial_comm.cpp similarity index 98% rename from src/serial_comm.cpp rename to core/serial_comm.cpp index f01c043..4584413 100644 --- a/src/serial_comm.cpp +++ b/core/serial_comm.cpp @@ -159,7 +159,7 @@ errCode SerialComm::send( const SerialCommProtoPacket& packet ) { return errCode::ERR_TIMEOUT; } - uint8_t tx_buffer[ SERIAL_COMM_MAX_PACKET_SIZE_V1 ]; + uint8_t tx_buffer[ SERIAL_COMM_MAX_PACKET_SIZE_V2 ]; size_t packet_len = SerialCommProtocol::encode( packet, @@ -208,7 +208,7 @@ void SerialComm::on_interbyte_timeout() { errCode SerialComm::register_callback( - SerialCommProtoCommand command, + SerialCommCommand command, packet_callback_t callback, void* ctx ) { @@ -220,7 +220,7 @@ errCode SerialComm::register_callback( } -errCode SerialComm::unregister_callback( SerialCommProtoCommand command ) { +errCode SerialComm::unregister_callback( SerialCommCommand command ) { return this->dispatcher_.unregister_callback( command ); } diff --git a/include/serial_comm.h b/core/serial_comm.h similarity index 98% rename from include/serial_comm.h rename to core/serial_comm.h index de73144..911b4ba 100644 --- a/include/serial_comm.h +++ b/core/serial_comm.h @@ -166,7 +166,7 @@ class SerialComm { * @return Middleware result */ errCode register_callback( - SerialCommProtoCommand command, + SerialCommCommand command, packet_callback_t callback, void* ctx ); @@ -177,7 +177,7 @@ class SerialComm { * @return Middleware result */ errCode unregister_callback( - SerialCommProtoCommand command + SerialCommCommand command ); private: diff --git a/include/serial_comm_config.h b/core/serial_comm_config.h similarity index 100% rename from include/serial_comm_config.h rename to core/serial_comm_config.h diff --git a/src/serial_comm_dispatcher.cpp b/core/serial_comm_dispatcher.cpp similarity index 98% rename from src/serial_comm_dispatcher.cpp rename to core/serial_comm_dispatcher.cpp index 6f61907..02b3336 100644 --- a/src/serial_comm_dispatcher.cpp +++ b/core/serial_comm_dispatcher.cpp @@ -151,7 +151,7 @@ errCode SerialCommDispatcher::enqueue( errCode SerialCommDispatcher::register_callback( - SerialCommProtoCommand command, + SerialCommCommand command, packet_callback_t callback, void* ctx ) { @@ -174,7 +174,7 @@ errCode SerialCommDispatcher::register_callback( errCode SerialCommDispatcher::unregister_callback( - SerialCommProtoCommand command + SerialCommCommand command ) { uint8_t cmd = static_cast( command ); xSemaphoreTake( this->callback_mutex_, portMAX_DELAY ); diff --git a/include/serial_comm_dispatcher.h b/core/serial_comm_dispatcher.h similarity index 98% rename from include/serial_comm_dispatcher.h rename to core/serial_comm_dispatcher.h index 5cec25b..c389716 100644 --- a/include/serial_comm_dispatcher.h +++ b/core/serial_comm_dispatcher.h @@ -189,7 +189,7 @@ class SerialCommDispatcher { * @return Result code */ SerialCommResult_Codes::errCode register_callback( - SerialCommProtoCommand command, + SerialCommCommand command, packet_callback_t callback, void* ctx ); @@ -200,7 +200,7 @@ class SerialCommDispatcher { * @return Result code */ SerialCommResult_Codes::errCode unregister_callback( - SerialCommProtoCommand command + SerialCommCommand command ); public: diff --git a/src/serial_comm_parser.cpp b/core/serial_comm_parser.cpp similarity index 99% rename from src/serial_comm_parser.cpp rename to core/serial_comm_parser.cpp index 2b58639..a4c30cb 100644 --- a/src/serial_comm_parser.cpp +++ b/core/serial_comm_parser.cpp @@ -72,7 +72,7 @@ bool SerialCommParser::parse_byte( // READ COMMAND case State::READ_COMMAND: { packet_.header.command = - static_cast(byte); + static_cast(byte); this->state_ = State::READ_LENGTH_L; break; } diff --git a/include/serial_comm_parser.h b/core/serial_comm_parser.h similarity index 100% rename from include/serial_comm_parser.h rename to core/serial_comm_parser.h diff --git a/src/serial_comm_protocol.cpp b/core/serial_comm_protocol.cpp similarity index 87% rename from src/serial_comm_protocol.cpp rename to core/serial_comm_protocol.cpp index b2368bf..61b3335 100644 --- a/src/serial_comm_protocol.cpp +++ b/core/serial_comm_protocol.cpp @@ -75,14 +75,15 @@ int32_t SerialCommProtocol::decode( return errCode::ERR_NULL_POINTER; } - // Check de maximum acceptable packet size - if ( raw_len > SERIAL_COMM_MAX_PACKET_SIZE_V1 ) { + // Check de maximum acceptable packet size (accept V2 max as upper bound) + if ( raw_len > SERIAL_COMM_MAX_PACKET_SIZE_V2 ) { return errCode::ERR_OVERFLOW; } // Check minimum size for a valid packet size_t min_packet_size = SERIAL_COMM_HEADER_SIZE + + SERIAL_COMM_SEQ_SIZE + SERIAL_COMM_PROTOCOL_VER_SIZE + SERIAL_COMM_COMMAND_SIZE + SERIAL_COMM_LENGTH_SIZE + @@ -129,6 +130,7 @@ int32_t SerialCommProtocol::decode( } size_t expected_size = SERIAL_COMM_HEADER_SIZE + + SERIAL_COMM_SEQ_SIZE + SERIAL_COMM_PROTOCOL_VER_SIZE + SERIAL_COMM_COMMAND_SIZE + SERIAL_COMM_LENGTH_SIZE + @@ -178,37 +180,30 @@ uint16_t SerialCommProtocol::compute_crc16( bool SerialCommProtocol::validate_crc( const SerialCommProtoPacket& packet ) { - uint8_t temp_buffer[ SERIAL_COMM_MAX_PACKET_SIZE_V1 ]; + uint8_t temp_buffer[ SERIAL_COMM_MAX_PACKET_SIZE_V2 ]; size_t index = 0; + // SEQ_ID (CRC excludes only the preamble header and the CRC field) + temp_buffer[index++] = (packet.header.seq_id & 0xFF); + temp_buffer[index++] = ((packet.header.seq_id >> 8) & 0xFF); + // VERSION temp_buffer[index++] = packet.header.version; // COMMAND - temp_buffer[index++] = - static_cast( - packet.header.command - ); + temp_buffer[index++] = static_cast(packet.header.command); // PAYLOAD LENGTH temp_buffer[index++] = (packet.header.payload_len & 0xFF); temp_buffer[index++] = ((packet.header.payload_len >> 8) & 0xFF); // PAYLOAD - memcpy( - &temp_buffer[index], - packet.payload, - packet.header.payload_len - ); + memcpy(&temp_buffer[index], packet.payload, packet.header.payload_len); index += packet.header.payload_len; // COMPUTE CRC16 TO COMPARE - uint16_t computed_crc = - compute_crc16( - temp_buffer, - index - ); - return ( computed_crc == packet.crc ); + uint16_t computed_crc = compute_crc16(temp_buffer, index); + return (computed_crc == packet.crc ); } @@ -236,6 +231,7 @@ size_t SerialCommProtocol::packet_size( ) { return SERIAL_COMM_HEADER_SIZE + + SERIAL_COMM_SEQ_SIZE + SERIAL_COMM_PROTOCOL_VER_SIZE + SERIAL_COMM_COMMAND_SIZE + SERIAL_COMM_LENGTH_SIZE + @@ -244,7 +240,7 @@ size_t SerialCommProtocol::packet_size( } void SerialCommProtocol::clear_packet( SerialCommProtoPacket& packet ) { - memset( &packet, 0, sizeof(SerialCommProtoPacket) ); + memset(&packet, 0, sizeof(SerialCommProtoPacket)); packet.header.version = SERIAL_COMM_PROTOCOL_VER1; - packet.header.command = SerialCommCommand::UNKNOWN; + packet.header.command = SerialCommCommand::UNDEFINED; } diff --git a/include/serial_comm_protocol.h b/core/serial_comm_protocol.h similarity index 100% rename from include/serial_comm_protocol.h rename to core/serial_comm_protocol.h diff --git a/include/serial_comm_transport.h b/core/serial_comm_transport.h similarity index 100% rename from include/serial_comm_transport.h rename to core/serial_comm_transport.h diff --git a/include/serial_comm_utils.h b/core/serial_comm_utils.h similarity index 100% rename from include/serial_comm_utils.h rename to core/serial_comm_utils.h diff --git a/core/serial_comm_watchdog.cpp b/core/serial_comm_watchdog.cpp new file mode 100644 index 0000000..62940f5 --- /dev/null +++ b/core/serial_comm_watchdog.cpp @@ -0,0 +1,162 @@ +/** + * @file serial_comm_watchdog.cpp + * @brief Serial communication watchdog timer implementation + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + + +#pragma once + +#include "serial_comm_watchdog.h" + + +static bool IRAM_ATTR SerialCommWatchdogTimer::timer_callback( + gptimer_handle_t timer, + const gptimer_alarm_event_data_t *data, + void *user_ctx +){ + (void)timer; + (void)data; + + // Gets the user context + SerialCommWatchdogTimer* usr_timer = + (SerialCommWatchdogTimer *)(user_ctx); + + // Send a notification to the watchdog task to call the callback + BaseType_t high_task_wakeup = pdFALSE; + vTaskNotifyGiveFromISR( + usr_timer->_taskHandle, + &high_task_wakeup + ); + return (high_task_wakeup == pdTRUE); +} + +/** + * @brief Watchdog task function + * @details This function is executed by the watchdog task and + * waits for timer notification events. When it receives + * a notification, it calls the user-defined callback. + */ +static void SerialCommWatchdogTimer::task_function(void *pvArg){ + SerialCommWatchdogTimer* timer = (SerialCommWatchdogTimer *)(pvArg); + while (true) { + ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); + timer->stop(); + if ( timer->_callback ) { + timer->_callback(); + } + } +} + +SerialCommWatchdogTimer::SerialCommWatchdogTimer( + const char* task_name = "SerialCommWatchdog", + uint64_t timeout_us = uart_interbyte_timeout_us(SERIAL_COMM_UART_BAUDRATE), + Callback callback = nullptr, + uint32_t stack_size = 1024*4, + UBaseType_t priority = 5 +): + _callback(callback), + _timeout_us(timeout_us), + task_name(task_name), + stack_size(stack_size), + priority(priority) +{ + xTaskCreate( + this->task_function, + this->task_name, + this->stack_size, + this, + this->priority, + &this->_taskHandle + ); + + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1000000, // 1 tick = 1 microsecond + }; + ESP_ERROR_CHECK( + gptimer_new_timer( + &timer_config, + &_timer + ) + ); + + gptimer_event_callbacks_t callbacks = { + .on_alarm = timer_callback, + }; + ESP_ERROR_CHECK( + gptimer_register_event_callbacks( + _timer, + &callbacks, + this + ) + ); + + gptimer_alarm_config_t alarm_config = { + .alarm_count = timeout_us, + .reload_count = 0, + .flags = { + .auto_reload_on_alarm = false, + } + }; + ESP_ERROR_CHECK( + gptimer_set_alarm_action( + _timer, + &alarm_config + ) + ); + ESP_ERROR_CHECK(gptimer_enable(_timer)); +} + + +esp_err_t SerialCommWatchdogTimer::start() { + if ( this->_running) { + return ESP_OK; + } + ESP_ERROR_CHECK( + gptimer_set_raw_count(this->_timer, 0) + ); + esp_err_t err = gptimer_start(this->_timer); + if (err == ESP_OK) { + this->_running = true; + } + return err; +} + + +esp_err_t SerialCommWatchdogTimer::stop(){ + if ( !this->_running ) { + return ESP_OK; + } + esp_err_t err = gptimer_stop( this->_timer); + if ( err == ESP_OK || err == ESP_ERR_INVALID_STATE ) { + this->_running = false; + return ESP_OK; + } + return err; +} + + +esp_err_t SerialCommWatchdogTimer::kick(){ + ESP_ERROR_CHECK(this->stop()); + ESP_ERROR_CHECK( + gptimer_set_raw_count(this->_timer, 0) + ); + return this->start(); +} + + +SerialCommWatchdogTimer::~SerialCommWatchdogTimer(){ + this->stop(); + if (this->_timer != nullptr) { + gptimer_disable(this->_timer); + gptimer_del_timer(this->_timer); + this->_timer = nullptr; + } + if (this->_taskHandle != nullptr) { + vTaskDelete(this->_taskHandle); + this->_taskHandle = nullptr; + } +} diff --git a/core/serial_comm_watchdog.h b/core/serial_comm_watchdog.h new file mode 100644 index 0000000..ca85b12 --- /dev/null +++ b/core/serial_comm_watchdog.h @@ -0,0 +1,137 @@ +#pragma once + +#include "serial_comm_config.h" +#include "serial_comm_utils.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" + +#include "esp_timer.h" +#include "esp_log.h" +#include "esp_err.h" + +#include "driver/gptimer.h" + +#include +#include +#include +#include + + +using namespace SerialCommUtils; + +/** + * @brief Serial communication middleware watchdog timer + * @details This class implements a watchdog timer using the ESP32's general + * purpose timer (GPTimer) and FreeRTOS tasks. It allows you to set + * a timeout duration and a callback function that will be called + * when the timer expires. The timer can be started, stopped, and + * kicked (reset) as needed. + */ +class SerialCommWatchdogTimer { + public: + /** + * @brief Callback function type for the watchdog + * @details The callback function is called when the timer expires + * and must be defined by the user to perform the desired + * action. + */ + using Callback = std::function; + + private: + gptimer_handle_t _timer = nullptr; + TaskHandle_t _taskHandle = nullptr; + Callback _callback; + + uint64_t _timeout_us = 0; + bool _running = false; + + const char *task_name; + uint32_t stack_size; + UBaseType_t priority; + + + /** + * @brief Timer Callback Function + * @details This function is called when the timer expires, and + * it sends a notification to the watchdog task to call + * the callback. + */ + static bool IRAM_ATTR timer_callback( + gptimer_handle_t timer, + const gptimer_alarm_event_data_t *data, + void *user_ctx + ); + + + /** + * @brief Watchdog task function + * @details This function is executed by the watchdog task and + * waits for timer notification events. When it receives + * a notification, it calls the user-defined callback. + */ + static void task_function(void *pvArg); + + + public: + /** + * @brief Constructor + * @details This constructor initializes the watchdog timer with the + * specified parameters. It creates a FreeRTOS task for the + * watchdog and configures the GPTimer with the specified + * timeout and callback. + * @param task_name Name of the FreeRTOS task for the watchdog + * @param timeout_us Timeout duration in microseconds + * @param callback User-defined callback function to call on timeout + * @param stack_size Stack size for the FreeRTOS task + * @param priority Priority for the FreeRTOS task + */ + SerialCommWatchdogTimer( + const char* task_name = "SerialCommWatchdog", + uint64_t timeout_us = uart_interbyte_timeout_us(SERIAL_COMM_UART_BAUDRATE), + Callback callback = nullptr, + uint32_t stack_size = 1024*4, + UBaseType_t priority = 5 + ); + + + /** + * @brief Start the watchdog timer + * @details This function starts the watchdog timer. If the timer + * is already running, it does nothing. + * @return ESP_OK if the timer was started successfully + * @return ESP_ERR_INVALID_STATE if the timer is already running + * @return Other error codes from the underlying GPTimer functions + */ + esp_err_t start(); + + + /** + * @brief Stop the watchdog timer + * @details This function stops the watchdog timer. If the timer + * is not running, it does nothing. + * @return ESP_OK if the timer was stopped successfully + * @return ESP_ERR_INVALID_STATE if the timer is not running + * @return Other error codes from the underlying GPTimer functions + */ + esp_err_t stop(); + + + /** + * @brief Kick (reset) the watchdog timer + * @details This function resets the watchdog timer by stopping it, + * setting the timer count back to zero, and starting it + * again. + * @return ESP_OK if the timer was kicked successfully + * @return Other error codes from the underlying GPTimer functions + * @return ESP_ERR_INVALID_STATE if the timer is not running + */ + esp_err_t kick(); + + + /** + * @brief Destructor + */ + ~SerialCommWatchdogTimer(); +}; \ No newline at end of file diff --git a/include/serial_comm_eventloop.h b/include/serial_comm_eventloop.h deleted file mode 100644 index 9e4c674..0000000 --- a/include/serial_comm_eventloop.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -/** - * @brief This file defines the event loop for the serial communication component. - */ - -#include "serial_comm_types.h" - -#include "esp_event.h" -#include "esp_err.h" - -using packet_callback_t = - void (*)(const Packet& pkt); - -class EventLoop { -public: - - static esp_err_t init(); - - static esp_err_t register_callback( - int32_t event_id, - esp_event_handler_t callback - ); - - static esp_err_t post_event( - int32_t event_id, - const Packet& pkt - ); - -private: - - static esp_event_loop_handle_t loop_; -}; diff --git a/include/serial_comm_events.h b/include/serial_comm_events.h deleted file mode 100644 index b92503a..0000000 --- a/include/serial_comm_events.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @brief This file defines the events for the serial communication component. - */ - -#pragma once - -#include "esp_event.h" - - -ESP_EVENT_DECLARE_BASE(SERIAL_COMM_EVENTS); - -enum EventID{ - EVENT_PACKET_RECEIVED = 0, - EVENT_READ_JOINTS, - EVENT_WRITE_JOINTS, - EVENT_PING, - EVENT_READ_UTILITY, - EVENT_WRITE_UTILITY, - EVENT_SMOOTH_MOVE, - EVENT_PROTOCOL_ERROR -}; diff --git a/include/serial_comm_watchdog.h b/include/serial_comm_watchdog.h deleted file mode 100644 index 9b6b3c3..0000000 --- a/include/serial_comm_watchdog.h +++ /dev/null @@ -1,211 +0,0 @@ -#pragma once - -#include "serial_comm_config.h" -#include "serial_comm_utils.h" - -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "freertos/task.h" - -#include "esp_timer.h" -#include "esp_log.h" -#include "esp_err.h" - -#include "driver/gptimer.h" - -#include -#include -#include -#include - - -using namespace SerialCommUtils; - -/** - * @brief Serial communication middleware watchdog timer - * @details This class implements a watchdog timer using the ESP32's general - * purpose timer (GPTimer) and FreeRTOS tasks. It allows you to set - * a timeout duration and a callback function that will be called - * when the timer expires. The timer can be started, stopped, and - * kicked (reset) as needed. - */ -class SerialCommWatchdogTimer { - public: - /** - * @brief Callback function type for the watchdog - * @details The callback function is called when the timer expires - * and must be defined by the user to perform the desired - * action. - */ - using Callback = std::function; - - private: - gptimer_handle_t _timer = nullptr; - TaskHandle_t _taskHandle = nullptr; - Callback _callback; - - uint64_t _timeout_us = 0; - bool _running = false; - - const char *task_name; - uint32_t stack_size; - UBaseType_t priority; - - - /** - * @brief Timer Callback Function - * @details This function is called when the timer expires, and - * it sends a notification to the watchdog task to call - * the callback. - */ - static bool IRAM_ATTR timer_callback( - gptimer_handle_t timer, - const gptimer_alarm_event_data_t *data, - void *user_ctx - ){ - (void)timer; - (void)data; - - // Gets the user context - SerialCommWatchdogTimer* usr_timer = - (SerialCommWatchdogTimer *)(user_ctx); - - // Send a notification to the watchdog task to call the callback - BaseType_t high_task_wakeup = pdFALSE; - vTaskNotifyGiveFromISR( - usr_timer->_taskHandle, - &high_task_wakeup - ); - return (high_task_wakeup == pdTRUE); - } - - /** - * @brief Watchdog task function - * @details This function is executed by the watchdog task and - * waits for timer notification events. When it receives - * a notification, it calls the user-defined callback. - */ - static void task_function(void *pvArg){ - SerialCommWatchdogTimer* timer = (SerialCommWatchdogTimer *)(pvArg); - while (true) { - ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); - timer->stop(); - if ( timer->_callback ) { - timer->_callback(); - } - } - } - - public: - SerialCommWatchdogTimer( - const char* task_name = "SerialCommWatchdog", - uint64_t timeout_us = uart_interbyte_timeout_us(SERIAL_COMM_UART_BAUDRATE), - Callback callback = nullptr, - uint32_t stack_size = 1024*4, - UBaseType_t priority = 5 - ): - _callback(callback), - _timeout_us(timeout_us), - task_name(task_name), - stack_size(stack_size), - priority(priority) - { - xTaskCreate( - this->task_function, - this->task_name, - this->stack_size, - this, - this->priority, - &this->_taskHandle - ); - - gptimer_config_t timer_config = { - .clk_src = GPTIMER_CLK_SRC_DEFAULT, - .direction = GPTIMER_COUNT_UP, - .resolution_hz = 1000000, // 1 tick = 1 microsecond - }; - ESP_ERROR_CHECK( - gptimer_new_timer( - &timer_config, - &_timer - ) - ); - - gptimer_event_callbacks_t callbacks = { - .on_alarm = timer_callback, - }; - ESP_ERROR_CHECK( - gptimer_register_event_callbacks( - _timer, - &callbacks, - this - ) - ); - - gptimer_alarm_config_t alarm_config = { - .alarm_count = timeout_us, - .reload_count = 0, - .flags = { - .auto_reload_on_alarm = false, - } - }; - ESP_ERROR_CHECK( - gptimer_set_alarm_action( - _timer, - &alarm_config - ) - ); - ESP_ERROR_CHECK(gptimer_enable(_timer)); - } - - - esp_err_t start() { - if ( this->_running) { - return ESP_OK; - } - ESP_ERROR_CHECK( - gptimer_set_raw_count(this->_timer, 0) - ); - esp_err_t err = gptimer_start(this->_timer); - if (err == ESP_OK) { - this->_running = true; - } - return err; - } - - - esp_err_t stop(){ - if ( !this->_running ) { - return ESP_OK; - } - esp_err_t err = gptimer_stop( this->_timer); - if ( err == ESP_OK || err == ESP_ERR_INVALID_STATE ) { - this->_running = false; - return ESP_OK; - } - return err; - } - - - esp_err_t kick(){ - ESP_ERROR_CHECK(this->stop()); - ESP_ERROR_CHECK( - gptimer_set_raw_count(this->_timer, 0) - ); - return this->start(); - } - - - ~SerialCommWatchdogTimer(){ - this->stop(); - if (this->_timer != nullptr) { - gptimer_disable(this->_timer); - gptimer_del_timer(this->_timer); - this->_timer = nullptr; - } - if (this->_taskHandle != nullptr) { - vTaskDelete(this->_taskHandle); - this->_taskHandle = nullptr; - } - } -}; \ No newline at end of file diff --git a/include/serial_comm_messages.h b/messages/serial_comm_messages.h similarity index 96% rename from include/serial_comm_messages.h rename to messages/serial_comm_messages.h index 2362509..98bc0bd 100644 --- a/include/serial_comm_messages.h +++ b/messages/serial_comm_messages.h @@ -43,8 +43,6 @@ const char* serial_command_to_str(SerialCommCommand cmd){ return "READ_UTILITY"; case SerialCommCommand::WRITE_UTILITY: return "WRITE_UTILITY"; - case SerialCommCommand::SMOOTH_MOVE: - return "SMOOTH_MOVE"; default: return "UNKNOWN_COMMAND"; } diff --git a/middleware/README.md b/middleware/README.md new file mode 100644 index 0000000..6eda539 --- /dev/null +++ b/middleware/README.md @@ -0,0 +1,17 @@ +# Middleware Layer + +## Orquestra abstrações semânticas + +Ele deve: + +- registrar services; +- registrar topics; +- registrar actions; +- integrar com SerialComm; +- integrar com TransactionManager; +- fazer roteamento semântico; +- encapsular packets; +- encapsular seq_id; +- encapsular replies. + +Ele abstrai o uso do SerialComm, Dispatcher e Parser do usuário. diff --git a/middleware/action/serial_comm_action.cpp b/middleware/action/serial_comm_action.cpp new file mode 100644 index 0000000..fe3d5b8 --- /dev/null +++ b/middleware/action/serial_comm_action.cpp @@ -0,0 +1,103 @@ +/** + * @file serial_comm_action.h + * @brief Generic Action abstraction for SerialComm middleware + * @details Provides a ROS-like action abstraction for long-running + * asynchronous tasks over the SerialComm protocol. + * + * Responsibilities: + * - Goal handling + * - Feedback handling + * - Result handling + * - Action execution abstraction + * - Cancel support (future) + * + * @note Initial implementation only defines the action abstraction. + * Full execution engine and state machine are future work. + * + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#include "serial_comm_action.h" + + +errCode SerialCommAction::init( + Command command, + goal_callback_t goal_cb, + feedback_callback_t feedback_cb = nullptr +) { + if ( goal_cb == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + this->command_ = command; + this->goal_callback_ = goal_cb; + this->feedback_callback_ = feedback_cb; + this->initialized_ = true; + return errCode::OK; +} + +bool SerialCommAction::execute_goal( const Goal& goal, Result& result ) { + if ( !this->initialized_ ) { + return false; + } + if ( this->goal_callback_ == nullptr ) { + return false; + } + return this->goal_callback_( goal, result ); +} + +void SerialCommAction::publish_feedback( const Feedback& feedback ) { + if ( !this->initialized_ ) { + return; + } + if ( this->feedback_callback_ == nullptr ) { + return; + } + this->feedback_callback_( feedback ); +} + +Command SerialCommAction::command() const override { + return this->command_; +} + + +errCode SerialCommAction::handle_packet( + const SerialCommProtoPacket& packet +){ + if ( !this->initialized_ ) { + return errCode::ERR_NOT_INITIALIZED; + } + Goal goal; + bool ret = Serializer::deserialize( + packet.payload, + packet.header.payload_len, + goal + ); + if ( !ret ) { + return errCode::ERR_PARSER; + } + Result result; + ret = this->execute_goal( goal, result ); + if ( !ret ) { + return errCode::ERR_FAIL; + } + + /** + * @TODO: Publish result packet + * - feedback stream + * - goal handling + * - cancelation + * - async execution support + */ + + return errCode::OK; +} + + +bool SerialCommAction::initialized() const { + return this->initialized_; +} + +bool SerialCommAction::valid() const { + return ( this->goal_callback_ != nullptr ); +} diff --git a/middleware/action/serial_comm_action.h b/middleware/action/serial_comm_action.h new file mode 100644 index 0000000..9dcd0ff --- /dev/null +++ b/middleware/action/serial_comm_action.h @@ -0,0 +1,163 @@ +/** + * @file serial_comm_action.h + * @brief Generic Action abstraction for SerialComm middleware + * @details Provides a ROS-like action abstraction for long-running + * asynchronous tasks over the SerialComm protocol. + * + * Responsibilities: + * - Goal handling + * - Feedback handling + * - Result handling + * - Action execution abstraction + * - Cancel support (future) + * + * @note Initial implementation only defines the action abstraction. + * Full execution engine and state machine are future work. + * + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#pragma once + +#include "messages/serial_comm_messages.h" + +#include "core/serial_comm_protocol.h" +#include "core/serial_comm_utils.h" + +#include "serial_comm_action_base.h" +#include "serial_comm_serializer.h" + +#include +#include + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + + +/** + * @brief Generic Action abstraction + * @tparam Goal Action goal type + * @tparam Feedback Action feedback type + * @tparam Result Action result type + */ +template< typename Goal, typename Feedback, typename Result > +class SerialCommAction : public IActionBase { + public: + + /** + * @brief Goal callback type + * @param goal Incoming goal + * @param result Final result + * @return true Goal accepted/executed + * @return false Goal rejected/failed + */ + using goal_callback_t = bool (*)( const Goal& goal, Result& result ); + + + /** + * @brief Feedback callback type + * @param feedback Action feedback + */ + using feedback_callback_t = void (*)( const Feedback& feedback ); + + + private: + + /** + * @brief Action command ID + */ + Command command_ = static_cast(0); + + /** + * @brief Goal callback + */ + goal_callback_t goal_callback_ = nullptr; + + /** + * @brief Feedback callback + */ + feedback_callback_t feedback_callback_ = nullptr; + bool initialized_ = false; + + + public: + + /** + * @brief Constructor + */ + SerialCommAction() = default; + + + public: + + /** + * @brief Initialize action + * @param command Action command ID + * @param goal_cb Goal callback + * @param feedback_cb Feedback callback + * @return Result code + */ + errCode init( + Command command, + goal_callback_t goal_cb, + feedback_callback_t feedback_cb = nullptr + ); + + + private: + /** + * @brief Execute action goal + * @param goal Goal object + * @param result Result object + * @return true Goal executed successfully + * @return false Goal execution failed + */ + bool execute_goal( const Goal& goal, Result& result ); + + + public: + /** + * @brief Publish feedback + * @param feedback Feedback object + */ + void publish_feedback( const Feedback& feedback ); + + + /** + * @brief Get action command ID + */ + Command command() const override; + + + /** + * @brief Handle incoming action packet + * @param packet Incoming action packet + * @return Result code + */ + errCode handle_packet( + const SerialCommProtoPacket& packet + ) override; + + + /** + * @brief Check if action is initialized + * @return true Action is initialized + * @return false Action is not initialized + */ + bool initialized() const; + + /** + * @brief Check if action is valid + * @return true Action is valid + * @return false Action is invalid (e.g. missing callbacks) + */ + bool valid() const; + + + public: + /** + * @brief Destructor + */ + virtual ~SerialCommAction() = default; +}; \ No newline at end of file diff --git a/middleware/action/serial_comm_action_base.h b/middleware/action/serial_comm_action_base.h new file mode 100644 index 0000000..8fd95d4 --- /dev/null +++ b/middleware/action/serial_comm_action_base.h @@ -0,0 +1,39 @@ +/** + * @file serial_comm_action_base.h + * @brief Base polymorphic interface for middleware actions + */ + +#pragma once + +#include "core/serial_comm_protocol.h" +#include "core/serial_comm_utils.h" + +#include + + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + +/** + * @brief Base action interface + */ +class IActionBase { + public: + virtual ~IActionBase() = default; + + public: + + /** + * @brief Get action command ID + */ + virtual Command command() const = 0; + + /** + * @brief Handle incoming action packet + * @param packet Incoming action packet + * @return Result code + */ + virtual errCode handle_packet( + const SerialCommProtoPacket& packet + ) = 0; +}; \ No newline at end of file diff --git a/middleware/serial_comm_manager.cpp b/middleware/serial_comm_manager.cpp new file mode 100644 index 0000000..979261c --- /dev/null +++ b/middleware/serial_comm_manager.cpp @@ -0,0 +1,301 @@ +/** + * @file serial_comm_manager.cpp + * @brief High-level semantic middleware manager implementation + */ + +#include "serial_comm_manager.h" + + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + + +static const char* TAG = "SERIAL_COMM_MANAGER"; + + +errCode SerialCommManager::init( const Config& cfg ) { + if ( this->serial_ == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + if ( this->initialized_ ) { + return errCode::ERR_ALREADY_INITIALIZED; + } + + this->cfg_ = cfg; + + // Create mutexes + this->seq_mutex_ = xSemaphoreCreateMutex(); + if ( this->seq_mutex_ == nullptr ) { + return errCode::ERR_NO_MEMORY; + } + this->registry_mutex_ = xSemaphoreCreateMutex(); + if ( this->registry_mutex_ == nullptr ) { + vSemaphoreDelete( this->seq_mutex_ ); + this->seq_mutex_ = nullptr; + return errCode::ERR_NO_MEMORY; + } + + // Clear registries + this->clear_registries(); + + // Init transaction manager + errCode res = this->transactions_.init( + cfg.enable_transactions, + cfg.service_timeout_ms + ); + if ( res != errCode::OK ) { + vSemaphoreDelete( this->seq_mutex_ ); + this->seq_mutex_ = nullptr; + vSemaphoreDelete( this->registry_mutex_ ); + this->registry_mutex_ = nullptr; + return res; + } + + // Register internal packet router for all commands + for ( uint16_t cmd = 0; cmd < 256; cmd++ ) { + this->serial_->register_callback( + static_cast(cmd), + serial_packet_callback, + this + ); + } + this->initialized_ = true; + ESP_LOGI( TAG, "SerialCommManager initialized" ); + return errCode::OK; +} + + +errCode SerialCommManager::start() { + if ( !this->initialized_ ) { + return errCode::ERR_NOT_INITIALIZED; + } + if ( this->running_ ) { + return errCode::ERR_INVALID_STATE; + } + this->running_ = true; + ESP_LOGI( TAG, "SerialCommManager started" ); + return errCode::OK; +} + + +errCode SerialCommManager::stop() { + if ( !this->running_ ) { + return errCode::ERR_INVALID_STATE; + } + this->running_ = false; + ESP_LOGI( TAG, "SerialCommManager stopped" ); + return errCode::OK; +} + + +errCode SerialCommManager::deinit() { + if ( this->running_ ) { + this->stop(); + } + this->transactions_.deinit(); + if ( this->seq_mutex_ != nullptr ) { + vSemaphoreDelete( this->seq_mutex_ ); + this->seq_mutex_ = nullptr; + } + if( this->registry_mutex_ != nullptr ) { + vSemaphoreDelete( this->registry_mutex_ ); + this->registry_mutex_ = nullptr; + } + this->clear_registries(); + this->initialized_ = false; + ESP_LOGI( TAG, "SerialCommManager deinitialized" ); + return errCode::OK; +} + + +void SerialCommManager::serial_packet_callback( + void* ctx, + const SerialCommProtoPacket& packet +) { + if ( ctx == nullptr ) { + return; + } + auto* self = static_cast( ctx ); + self->route_packet(packet); +} + + +void SerialCommManager::handle_reply( + const SerialCommProtoPacket& packet +) { + ESP_LOGD( + TAG, + "Reply packet received: cmd=0x%02X seq=%u len=%u", + static_cast( packet.header.command ), + packet.header.seq_id, + packet.header.payload_len + ); + // Try to resolve transaction + errCode res = this->transactions_.resolve_transaction( packet ); + if ( res != errCode::OK ) { + ESP_LOGW( + TAG, + "Failed to resolve transaction for reply: seq=%u err=%s", + packet.header.seq_id, + err_to_str( res ) + ); + } else { + ESP_LOGD( + TAG, + "Transaction resolved for reply: seq=%u", + packet.header.seq_id + ); + } +} + + +uint16_t SerialCommManager::allocate_seq_id() { + uint16_t seq = 0; + if ( xSemaphoreTake( this->seq_mutex_, portMAX_DELAY ) == pdTRUE ) { + seq = this->next_seq_id_++; + if ( this->next_seq_id_ == 0 ) { + this->next_seq_id_ = 1; + } + xSemaphoreGive( this->seq_mutex_ ); + } + return seq; +} + + +errCode SerialCommManager::build_packet( + Command command, + uint16_t seq_id, + const uint8_t* payload, + size_t payload_len, + SerialCommProtoPacket& out_packet +) { + if ( payload_len > SERIAL_COMM_MAX_PAYLOAD_V2 + ) { + return errCode::ERR_INVALID_ARG; + } + SerialCommProtocol::clear_packet( out_packet ); + out_packet.header.seq_id = seq_id; + out_packet.header.version = SERIAL_COMM_PROTOCOL_VER1; + out_packet.header.command = command; + out_packet.header.payload_len = payload_len; + if ( payload != nullptr && payload_len > 0 ) { + memcpy( out_packet.payload, payload, payload_len ); + } + return errCode::OK; +} + + +bool SerialCommManager::initialized() const { + return this->initialized_; +} + + +bool SerialCommManager::running() const { + return this->running_; +} + + +uint16_t SerialCommManager::current_seq_id() const { + return this->next_seq_id_; +} + + +void SerialCommManager::clear_registries() { + memset( this->services_, 0, sizeof(this->services_) ); + memset( this->topics_, 0, sizeof(this->topics_) ); + memset( this->actions_, 0, sizeof(this->actions_) ); +} + + +errCode SerialCommManager::send_reply( + Command command, + uint16_t seq_id, + const uint8_t* payload, + size_t payload_len +) { + SerialCommProtoPacket packet; + errCode err = build_packet( + make_reply(command), seq_id, payload, payload_len, packet + ); + if ( err != errCode::OK ) { + return err; + } + return this->serial_->send( packet ); +} + + + +void SerialCommManager::route_packet( const SerialCommProtoPacket& packet ) { + if ( is_reply( packet.header.command ) ) { + this->handle_reply(packet); + return; + } + if ( this->is_service_command( packet.header.command ) ) { + this->handle_service_request(packet); + return; + } + if ( this->is_topic_command( packet.header.command ) ) { + this->handle_topic_message( packet ); + return; + } + if ( this->is_action_command( packet.header.command ) ) { + this->handle_action_packet( packet ); + return; + } + ESP_LOGW( + TAG, + "Unhandled packet command=0x%02X", + static_cast( packet.header.command ) + ); +} + + +void SerialCommManager::handle_topic_message( const SerialCommProtoPacket& packet ) { + ESP_LOGD( + TAG, + "Topic packet received: cmd=0x%02X seq=%u len=%u", + static_cast( packet.header.command ), + packet.header.seq_id, + packet.header.payload_len + ); + auto* entry = find_topic( packet.header.command ); + if ( entry == nullptr || entry->topic == nullptr ) { + ESP_LOGW(TAG, "No topic handler for command=0x%02X", static_cast( packet.header.command )); + return; + } + errCode res = entry->topic->handle_packet( packet ); + if ( res != errCode::OK ) { + ESP_LOGW(TAG, "Topic handler failed: cmd=0x%02X err=%s", static_cast(packet.header.command), err_to_str(res)); + } +} + + +void SerialCommManager::handle_action_packet( const SerialCommProtoPacket& packet ) { + ESP_LOGD( + TAG, + "Action packet received: cmd=0x%02X seq=%u len=%u", + static_cast( packet.header.command ), + packet.header.seq_id, + packet.header.payload_len + ); + auto* entry = find_action( packet.header.command ); + if ( entry == nullptr || entry->action == nullptr ) { + ESP_LOGW(TAG, "No action handler for command=0x%02X", static_cast( packet.header.command )); + return; + } + errCode res = entry->action->handle_packet( packet ); + if ( res != errCode::OK ) { + ESP_LOGW(TAG, "Action handler failed: cmd=0x%02X err=%s", static_cast(packet.header.command), err_to_str(res)); + } +} + + +void SerialCommManager::handle_request( const SerialCommProtoPacket& packet ) { + ESP_LOGD(TAG, "handle_request called for cmd=0x%02X (not implemented fully)", static_cast(packet.header.command)); +} + + +SerialCommManager::~SerialCommManager() { + this->deinit(); +} + diff --git a/middleware/serial_comm_manager.h b/middleware/serial_comm_manager.h new file mode 100644 index 0000000..4d4893b --- /dev/null +++ b/middleware/serial_comm_manager.h @@ -0,0 +1,465 @@ +/** + * @file serial_comm_manager.h + * @brief High-level semantic middleware manager for SerialComm + * @details Responsible for: + * - Service orchestration + * - Topic orchestration + * - Action orchestration + * - Transaction management + * - Packet semantic routing + * - Request/Reply abstraction + * - Typed communication API + * + * This class is the main public middleware API and abstracts + * low-level packet manipulation from the application layer. + * + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#pragma once + + +#include "core/serial_comm_messages.h" +#include "core/serial_comm_protocol.h" +#include "core/serial_comm_utils.h" +#include "core/serial_comm.h" + +#include "middleware/serial_comm_transaction_manager.h" +#include "middleware/serial_comm_serializer.h" + +#include "middleware/service/serial_comm_service.h" +#include "middleware/action/serial_comm_action.h" +#include "middleware/topic/serial_comm_topic.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#include +#include + + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + +// Alias for command type used across the middleware +using Command = SerialCommCommand; + + +/** + * @brief High-level Serial Communication Middleware Manager + */ +class SerialCommManager { + private: + static constexpr size_t MAX_SERVICES = 32; + static constexpr size_t MAX_TOPICS = 32; + static constexpr size_t MAX_ACTIONS = 16; + + // Service, Topic and Action registries + private: + struct ServiceEntry { + bool used = false; + Command command = static_cast(0); + IServiceBase* service = nullptr; + }; + struct TopicEntry { + bool used = false; + Command command = static_cast(0); + ITopicBase* topic = nullptr; + }; + struct ActionEntry { + bool used = false; + Command command = static_cast(0); + IActionBase* action = nullptr; + }; + + private: + ServiceEntry services_[ MAX_SERVICES ]; + TopicEntry topics_[ MAX_TOPICS ]; + ActionEntry actions_[ MAX_ACTIONS ]; + + + private: + SerialCommTransactionManager transactions_; + SemaphoreHandle_t registry_mutex_ = nullptr; + + + public: + + /** + * @brief Middleware runtime configuration + * @param enable_auto_reply If true, the manager will + * automatically send replies for handled requests + * @param enable_transactions If true, the manager will manage + * request-reply transactions and timeouts + * @param service_timeout_ms Default timeout for service calls + * in milliseconds + * @param enable_logs If true, the manager will output logs for + * operations and errors + */ + struct Config { + bool enable_auto_reply = true; + bool enable_transactions = true; + uint32_t service_timeout_ms = 1000; + bool enable_logs = true; + }; + + private: + + /* Underlying SerialComm instance */ + SerialComm* serial_ = nullptr; + Config cfg_; + + bool initialized_ = false; + bool running_ = false; + + /* Sequence ID generator */ + uint16_t next_seq_id_ = 1; + /* Sequence ID protection mutex */ + SemaphoreHandle_t seq_mutex_ = nullptr; + + + public: + + /** + * @brief Constructor + * @param serial Underlying SerialComm instance + */ + explicit SerialCommManager( SerialComm* serial ) + : serial_( serial ) { } + + /* Delete copy constructor and assignment operator */ + SerialCommManager( const SerialCommManager& ) = delete; + SerialCommManager& operator=(const SerialCommManager& ) = delete; + /* Delete move constructor and assignment operator */ + SerialCommManager( SerialCommManager&& ) = delete; + SerialCommManager& operator=( SerialCommManager&& ) = delete; + + + public: + + /** + * @brief Initialize middleware manager + * @param cfg Runtime configuration + * @return Result code + */ + errCode init( const Config& cfg); + + /** + * @brief Start middleware manager + * @return Result code + */ + errCode start(); + + /** + * @brief Stop middleware manager + * @return Result code + */ + errCode stop(); + + /** + * @brief Deinitialize middleware manager + * @return Result code + */ + errCode deinit(); + + + // SERVICES + public: + + /** + * @brief Create service server + * @tparam Req Request type + * @tparam Res Response type + * @return Result code + */ + template< typename Req, typename Res > + errCode create_service( + SerialCommService* service, + ); + + + /** + * @brief Call remote service + * @tparam Req Request type + * @tparam Res Response type + * @param command Service command ID + * @param request Request object + * @param response Output response object + * @param timeout_ms Timeout in milliseconds + * @return Result code + */ + template< typename Req, typename Res> + errCode call_service( + Command command, + const Req& request, + Res& response, + uint32_t timeout_ms = portMAX_DELAY + ); + + + // TOPICS + public: + + /** + * @brief Create topic publisher + * @tparam Msg Topic message type + * @return Result code + */ + template< typename Msg > + errCode create_publisher( + SerialCommTopic* publisher + ); + + + /** + * @brief Create topic subscription + * @tparam Msg Topic message type + * @return Result code + */ + template< typename Msg > + errCode create_subscription( + SerialCommTopic* subscription + ); + + + /** + * @brief Publish topic message + * @tparam Msg Topic message type + * @param command Topic command ID + * @param msg Message object + * @return Result code + */ + template< typename Msg > + errCode publish( Command command, const Msg& msg ); + + + // ACTIONS + public: + + /** + * @brief Create action server + * @tparam Goal Action goal type + * @tparam Feedback Action feedback type + * @tparam Result Action result type + * @param command Action command ID + * @param callback Action callback + * @return Result code + * @note Future implementation + */ + template< typename Goal, typename Feedback, typename Result > + errCode create_action( + SerialCommAction* action + ); + + + // INTERNAL ROUTING + private: + + /** + * @brief Internal packet callback from SerialComm + * @param ctx User context (pointer to this manager instance) + * @param packet Received protocol packet + */ + static void serial_packet_callback( + void* ctx, + const SerialCommProtoPacket& packet + ); + + /** + * @brief Route received packet + * @param packet Incoming packet + */ + void route_packet( const SerialCommProtoPacket& packet ); + + /** + * @brief Handle request packet + * @param packet Incoming request packet + */ + void handle_request( const SerialCommProtoPacket& packet ); + + /** + * @brief Handle reply packet + * @param packet Incoming reply packet + */ + void handle_reply( const SerialCommProtoPacket& packet ); + + + // UTILITIES + private: + + /** + * @brief Generate next sequence ID + * @return Sequence ID + */ + uint16_t allocate_seq_id(); + + /** + * @brief Build protocol packet + * @param command Service command ID + * @param seq_id Sequence ID + * @param payload Payload data + * @param payload_len Payload length + * @param out_packet Output protocol packet + * @return Result code + */ + errCode build_packet( + Command command, + uint16_t seq_id, + const uint8_t* payload, + size_t payload_len, + SerialCommProtoPacket& out_packet + ); + + /** + * @brief Send reply packet + * @param command Service command ID + * @param seq_id Sequence ID to reply to + * @param payload Payload data + * @param payload_len Payload length + * @return Result code + */ + errCode send_reply( + Command command, + uint16_t seq_id, + const uint8_t* payload, + size_t payload_len + ); + + /** + * @brief Service send reply message helper + * @tparam Res Response type + * @param command Service command ID + * @param seq_id Sequence ID to reply to + * @param response Response object to serialize and send + * @return Result code + */ + template< typename Res > + errCode send_reply_message( + Command command, + uint16_t seq_id, + const Res& response + ); + + /** + * @brief Publish topic message helper + * @tparam Msg Topic message type + * @param command Topic command ID + * @param message Message object to serialize and publish + * @return Result code + */ + template< typename Msg > + errCode publish( + Command command, + const Msg& message + ); + + + // HELPERS + private: + + /** + * @brief Find service entry by command ID + * @param command Service command ID + * @return Pointer to service entry or nullptr if not found + */ + ServiceEntry* find_service( Command command ); + + + /** + * @brief Find topic entry by command ID + * @param command Topic command ID + * @return Pointer to topic entry or nullptr if not found + */ + TopicEntry* find_topic( Command command ); + + + /** + * @brief Find action entry by command ID + * @param command Action command ID + * @return Pointer to action entry or nullptr if not found + */ + ActionEntry* find_action( Command command ); + + + /** + * @brief Check if command ID corresponds to a service + * @param command Command ID + * @return True if it's a service command + * @result False if it's not a service command + */ + bool is_service_command( Command command ) const; + + + /** + * @brief Check if command ID corresponds to a topic + * @param command Command ID + * @return True if it's a topic command + * @result False if it's not a topic command + */ + bool is_topic_command( Command command ) const; + + + /** + * @brief Check if command ID corresponds to an action + * @param command Command ID + * @return True if it's an action command + * @result False if it's not an action command + */ + bool is_action_command( Command command ) const; + + + /** + * @brief Clear all service, topic and action registries + */ + void clear_registries(); + + + // ROUTERS + private: + + /** + * @brief Route service request packet + * @param packet Incoming service request packet + */ + void handle_service_request( const SerialCommProtoPacket& packet ); + + /** + * @brief Route topic message packet + * @param packet Incoming topic message packet + */ + void handle_topic_message( const SerialCommProtoPacket& packet ); + + /** + * @brief Route action goal/feedback/result packet + * @param packet Incoming action packet + */ + void handle_action_packet( const SerialCommProtoPacket& packet ); + + + // GETTERS + public: + + /** + * @brief Check if manager is initialized + */ + bool initialized() const; + + /** + * @brief Check if manager is running + */ + bool running() const; + + /** + * @brief Get next sequence ID value + */ + uint16_t current_seq_id() const; + + + public: + + /** + * @brief Destructor + */ + virtual ~SerialCommManager(); +}; \ No newline at end of file diff --git a/middleware/serial_comm_manager_action.cpp b/middleware/serial_comm_manager_action.cpp new file mode 100644 index 0000000..fd647da --- /dev/null +++ b/middleware/serial_comm_manager_action.cpp @@ -0,0 +1,64 @@ +/** + * @file serial_comm_manager_action.cpp + * @brief SerialCommManager action handling implementation + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#include "serial_comm_manager.h" + +template< typename Goal, typename Feedback, typename Result > +errCode SerialCommManager::create_action( + SerialCommAction< Goal, Feedback, Result >* action +) { + if ( action == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + if ( !action->initialized() ) { + return errCode::ERR_NOT_INITIALIZED; + } + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < MAX_ACTIONS; i++ ) { + if ( !this->actions_[i].used ) { + this->actions_[i].used = true; + this->actions_[i].command = action->command(); + this->actions_[i].action = action; + xSemaphoreGive( this->registry_mutex_ ); + return errCode::OK; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return errCode::ERR_NO_MEMORY; +} + + + +bool SerialCommManager::is_action_command( Command command ) const { + for ( size_t i = 0; i < MAX_ACTIONS; i++ ) { + if ( + this->actions_[i].used && + this->actions_[i].command == command + ) { + return true; + } + } + return false; +} + + +SerialCommManager::ActionEntry* SerialCommManager::find_action( + Command command +) { + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < MAX_ACTIONS; i++ ) { + if ( + this->actions_[i].used && + this->actions_[i].command == command + ) { + xSemaphoreGive( this->registry_mutex_ ); + return &this->actions_[i]; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return nullptr; +} \ No newline at end of file diff --git a/middleware/serial_comm_manager_service.cpp b/middleware/serial_comm_manager_service.cpp new file mode 100644 index 0000000..238315e --- /dev/null +++ b/middleware/serial_comm_manager_service.cpp @@ -0,0 +1,159 @@ +/** + * @file serial_comm_manager_service.cpp + * @brief Serial communication middleware service handling + * implementation + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#include "serial_comm_manager.h" + + +template +errCode SerialCommManager::create_service( + SerialCommService* service +) { + if ( service == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + if ( !service->initialized() ) { + return errCode::ERR_NOT_INITIALIZED; + } + if ( xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ) != pdTRUE ) { + return errCode::ERR_TIMEOUT; + } + for ( size_t i = 0; i < MAX_SERVICES; i++ ) { + if ( !this->services_[i].used ) { + this->services_[i].used = true; + this->services_[i].command = service->command(); + this->services_[i].service = service; + xSemaphoreGive( this->registry_mutex_ ); + return errCode::OK; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return errCode::ERR_NO_MEMORY; +} + + +template +errCode SerialCommManager::call_service( + Command command, + const Req& request, + Res& response, + uint32_t timeout_ms +) { + uint8_t payload[ SERIAL_COMM_MAX_PAYLOAD_V1 ]; + size_t payload_size = 0; + + // SERIALIZE REQUEST + bool ok = Serializer::serialize( + request, + payload, + sizeof(payload), + payload_size + ); + if ( !ok ) { + return errCode::ERR_FAIL; + } + // ALLOCATE SEQ ID + uint16_t seq_id = this->allocate_seq_id(); + + // CREATE TRANSACTION + errCode err = this->transactions_.create_transaction( seq_id ); + if ( err != errCode::OK ) { + return err; + } + + // BUILD REQUEST PACKET + SerialCommProtoPacket request_packet; + err = build_packet( + command, + seq_id, + payload, + payload_size, + request_packet + ); + if ( err != errCode::OK ) { + return err; + } + + // SEND REQUEST + err = this->serial_->send( request_packet ); + if ( err != errCode::OK ) { + return err; + } + + // WAIT REPLY + SerialCommProtoPacket reply_packet; + err = this->transactions_.wait_reply( + seq_id, + reply_packet, + timeout_ms + ); + if ( err != errCode::OK ) { + return err; + } + + // DESERIALIZE RESPONSE + ok = Serializer::deserialize( + reply_packet.payload, + reply_packet.header.payload_len, + response + ); + if ( !ok ) { + return errCode::ERR_FAIL; + } + return errCode::OK; +} + + +void SerialCommManager::handle_service_request( + const SerialCommProtoPacket& packet +) { + ESP_LOGD( + TAG, + "Request packet received: cmd=0x%02X seq=%u len=%u", + static_cast( packet.header.command ), + packet.header.seq_id, + packet.header.payload_len + ); + + /** + * TODO: + * - SERVICE ROUTING + * - TOPIC ROUTING + * - ACTION ROUTING + * --------------------------------------------------------------------- */ +} + + +SerialCommManager::ServiceEntry* SerialCommManager::find_service( + Command command +) { + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < MAX_SERVICES; i++ ) { + if ( + this->services_[i].used && + this->services_[i].command == command + ) { + xSemaphoreGive( this->registry_mutex_ ); + return &this->services_[i]; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return nullptr; +} + + +bool SerialCommManager::is_service_command( Command command ) const { + for ( size_t i = 0; i < MAX_SERVICES; i++ ) { + if ( + this->services_[i].used && + this->services_[i].command == command + ) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/middleware/serial_comm_manager_topic.cpp b/middleware/serial_comm_manager_topic.cpp new file mode 100644 index 0000000..8b07f32 --- /dev/null +++ b/middleware/serial_comm_manager_topic.cpp @@ -0,0 +1,124 @@ +/** + * @file serial_comm_manager_topic.cpp + * @brief SerialCommManager topic handling implementation + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#include "serial_comm_manager.h" + + +template +errCode SerialCommManager::create_subscription( + SerialCommTopic* subscription +) { + if ( subscription == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + if ( !subscription->initialized() ) { + return errCode::ERR_NOT_INITIALIZED; + } + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + + for ( size_t i = 0; i < MAX_TOPICS; i++) { + if ( !this->topics_[i].used ) { + this->topics_[i].used = true; + this->topics_[i].command = subscription->command(); + this->topics_[i].topic = subscription; + xSemaphoreGive( this->registry_mutex_ ); + return errCode::OK; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return errCode::ERR_NO_MEMORY; +} + + +template +errCode SerialCommManager::create_publisher( + SerialCommTopic* publisher +) { + if ( publisher == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + if ( !publisher->initialized() ) { + return errCode::ERR_NOT_INITIALIZED; + } + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < MAX_TOPICS; i++) { + if ( !this->topics_[i].used ) { + this->topics_[i].used = true; + this->topics_[i].command = publisher->command(); + this->topics_[i].topic = publisher; + xSemaphoreGive( this->registry_mutex_ ); + return errCode::OK; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return errCode::ERR_NO_MEMORY; +} + + +template +errCode SerialCommManager::publish( + Command command, + const Msg& msg +) { + uint8_t payload[ SERIAL_COMM_MAX_PAYLOAD_V1 ]; + size_t payload_size = 0; + bool ok = Serializer::serialize( + msg, + payload, + sizeof(payload), + payload_size + ); + if ( !ok ) { + return errCode::ERR_FAIL; + } + + SerialCommProtoPacket packet; + errCode err = build_packet( + command, + 0, + payload, + payload_size, + packet + ); + if ( err != errCode::OK ) { + return err; + } + return this->serial_->send( + packet + ); +} + + +SerialCommManager::TopicEntry* SerialCommManager::find_topic( + Command command +) { + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < MAX_TOPICS; i++ ) { + if ( + this->topics_[i].used && + this->topics_[i].command == command + ) { + xSemaphoreGive( this->registry_mutex_ ); + return &this->topics_[i]; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return nullptr; +} + + +bool SerialCommManager::is_topic_command( Command command ) const { + for ( size_t i = 0; i < MAX_TOPICS; i++ ) { + if ( + this->topics_[i].used && + this->topics_[i].command == command + ) { + return true; + } + } + return false; +} diff --git a/middleware/serial_comm_serializer.h b/middleware/serial_comm_serializer.h new file mode 100644 index 0000000..238874c --- /dev/null +++ b/middleware/serial_comm_serializer.h @@ -0,0 +1,79 @@ +/** + * @file serial_comm_serializer.h + * @brief Generic serialization abstraction for SerialComm middleware + * @details Provides typed serialization/deserialization interfaces for + * middleware messages. + * + * Responsibilities: + * - Struct -> Payload serialization + * - Payload -> Struct deserialization + * - Middleware message ABI abstraction + * + * @note This is intentionally generic and must be specialized by the + * application layer for each message type. + * + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#pragma once + +#include "core/serial_comm_utils.h" + +#include +#include +#include + + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + +/** + * @brief Generic serializer interface + * @tparam T Message type + */ +template +struct Serializer { + + /** + * @brief Serialize message into payload buffer + * @param[in] msg Message object + * @param[out] buffer Output payload buffer + * @param[in] buffer_size Buffer capacity + * @param[out] serialized_size Bytes written + * @return true if serialization succeeded + * @return false otherwise + */ + static bool serialize( + const T& msg, + uint8_t* buffer, + size_t buffer_size, + size_t& serialized_size + ) { + (void)msg; + (void)buffer; + (void)buffer_size; + (void)serialized_size; + return false; + } + + + /** + * @brief Deserialize payload buffer into message object + * @param[in] buffer Input payload buffer + * @param[in] buffer_size Payload size + * @param[out] msg Output message object + * @return true if deserialization succeeded + * @return false otherwise + */ + static bool deserialize( + const uint8_t* buffer, + size_t buffer_size, + T& msg + ) { + (void)buffer; + (void)buffer_size; + (void)msg; + return false; + } +}; \ No newline at end of file diff --git a/middleware/serial_comm_transaction_manager.cpp b/middleware/serial_comm_transaction_manager.cpp new file mode 100644 index 0000000..d139fcd --- /dev/null +++ b/middleware/serial_comm_transaction_manager.cpp @@ -0,0 +1,152 @@ +/** + * @file serial_comm_transaction_manager.h + * @brief Transaction manager for SerialComm middleware + * @details Responsible for: + * - Pending request tracking + * - Reply matching using seq_id + * - Request timeout management + * - Synchronous service waiting + * - Transaction lifecycle management + * + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + + +#include "serial_comm_transaction_manager.h" + + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + + +errCode SerialCommTransactionManager::init() { + if ( this->initialized_ ) { + return errCode::ERR_ALREADY_INITIALIZED; + } + this->mutex_ = xSemaphoreCreateMutex(); + if ( this->mutex_ == nullptr ) { + return errCode::ERR_NO_MEMORY; + } + for ( size_t i = 0; i < SERIAL_COMM_MAX_TRANSACTIONS; i++ ) { + this->transactions_[i].semaphore = xSemaphoreCreateBinary(); + if ( this->transactions_[i].semaphore == nullptr ) { + return errCode::ERR_NO_MEMORY; + } + } + this->initialized_ = true; + return errCode::OK; +} + + +errCode SerialCommTransactionManager::deinit() { + for ( size_t i = 0; i < SERIAL_COMM_MAX_TRANSACTIONS; i++ ) { + if ( this->transactions_[i].semaphore != nullptr ) { + vSemaphoreDelete( this->transactions_[i].semaphore ); + this->transactions_[i].semaphore = nullptr; + } + } + if ( this->mutex_ != nullptr ) { + vSemaphoreDelete( this->mutex_ ); + this->mutex_ = nullptr; + } + this->initialized_ = false; + return errCode::OK; +} + + +errCode SerialCommTransactionManager::create_transaction( uint16_t seq_id ) { + if ( !this->initialized_ ) { + return errCode::ERR_NOT_INITIALIZED; + } + xSemaphoreTake( this->mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < SERIAL_COMM_MAX_TRANSACTIONS; i++ ) { + if ( !this->transactions_[i].active ) { + this->transactions_[i].active = true; + this->transactions_[i].completed = false; + this->transactions_[i].seq_id = seq_id; + xSemaphoreGive( this->mutex_ ); + return errCode::OK; + } + } + xSemaphoreGive( this->mutex_ ); + return errCode::ERR_NO_MEMORY; +} + + +errCode SerialCommTransactionManager::wait_reply( + uint16_t seq_id, + SerialCommProtoPacket& out_reply, + uint32_t timeout_ms +) { + Transaction* transaction = this->find_transaction(seq_id); + if ( transaction == nullptr ) { + return errCode::ERR_NOT_FOUND; + } + if ( xSemaphoreTake( + transaction->semaphore, + pdMS_TO_TICKS(timeout_ms) + ) != pdTRUE + ) { + this->destroy_transaction(seq_id); + return errCode::ERR_TIMEOUT; + } + out_reply = transaction->reply; + this->destroy_transaction(seq_id); + return errCode::OK; +} + + +errCode SerialCommTransactionManager::resolve_transaction( + const SerialCommProtoPacket& packet +) { + Transaction* transaction = + this->find_transaction( packet.header.seq_id ); + if ( transaction == nullptr ) { + return errCode::ERR_NOT_FOUND; + } + transaction->reply = packet; + transaction->completed = true; + xSemaphoreGive( transaction->semaphore ); + return errCode::OK; +} + + +Transaction* SerialCommTransactionManager::find_transaction( uint16_t seq_id ) { + for ( size_t i = 0; i < SERIAL_COMM_MAX_TRANSACTIONS; i++ ) { + if ( + this->transactions_[i].active && + this->transactions_[i].seq_id == seq_id + ) { + return &this->transactions_[i]; + } + } + return nullptr; +} + + +void SerialCommTransactionManager::destroy_transaction( uint16_t seq_id ) { + xSemaphoreTake( this->mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < SERIAL_COMM_MAX_TRANSACTIONS; i++ ) { + if ( + this->transactions_[i].active && + this->transactions_[i].seq_id == seq_id + ) { + this->transactions_[i].active = false; + this->transactions_[i].completed = false; + this->transactions_[i].seq_id = 0; + memset( + &this->transactions_[i].reply, + 0, + sizeof( SerialCommProtoPacket ) + ); + break; + } + } + xSemaphoreGive( this->mutex_ ); +} + + +SerialCommTransactionManager::~SerialCommTransactionManager() { + trhis->deinit(); +}; diff --git a/middleware/serial_comm_transaction_manager.h b/middleware/serial_comm_transaction_manager.h new file mode 100644 index 0000000..e5897b7 --- /dev/null +++ b/middleware/serial_comm_transaction_manager.h @@ -0,0 +1,163 @@ +/** + * @file serial_comm_transaction_manager.h + * @brief Transaction manager for SerialComm middleware + * @details Responsible for: + * - Pending request tracking + * - Reply matching using seq_id + * - Request timeout management + * - Synchronous service waiting + * - Transaction lifecycle management + * + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#pragma once + +#include "messages/serial_comm_messages.h" + +#include "core/serial_comm_protocol.h" +#include "core/serial_comm_utils.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#include +#include +#include + + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + + +/** + * @brief Maximum simultaneous transactions + */ +#ifndef SERIAL_COMM_MAX_TRANSACTIONS + #define SERIAL_COMM_MAX_TRANSACTIONS 16 +#endif + + +/** + * @brief Transaction manager + */ +class SerialCommTransactionManager { + private: + /** + * @brief Pending transaction entry + * @param active Whether the transaction slot is active + * @param seq_id Sequence ID of the transaction + * @param completed Whether the transaction has been completed + * @param reply Reply packet for the transaction + * @param semaphore Semaphore for transaction synchronization + */ + struct Transaction { + bool active = false; + uint16_t seq_id = 0; + bool completed = false; + SerialCommProtoPacket reply; + SemaphoreHandle_t semaphore = nullptr; + }; + + + private: + + /** + * @brief Transaction table + * TODO: Fixed-size array of transactions for simplicity. + * In a real implementation, a more efficient data structure + * (e.g., hash map) could be used. + */ + Transaction transactions_[ SERIAL_COMM_MAX_TRANSACTIONS ]; + + /** + * @brief Internal protection mutex + */ + SemaphoreHandle_t mutex_ = nullptr; + bool initialized_ = false; + + + public: + + /** + * @brief Constructor + */ + SerialCommTransactionManager() = default; + + /* Delete copy constructor and assignment operator */ + SerialCommTransactionManager( const SerialCommTransactionManager& ) = delete; + SerialCommTransactionManager& operator=(const SerialCommTransactionManager& ) = delete; + + + public: + + /** + * @brief Initialize transaction manager + * @return Result code + */ + errCode init(); + + + /** + * @brief Deinitialize transaction manager + * @return Result code + */ + errCode deinit(); + + + public: + + /** + * @brief Allocate transaction slot + * @param seq_id Transaction sequence ID + * @return Result code + */ + errCode create_transaction( uint16_t seq_id ); + + + /** + * @brief Wait for transaction reply + * @param seq_id Transaction sequence ID + * @param out_reply Output reply packet + * @param timeout_ms Wait timeout + * @return Result code + */ + errCode wait_reply( + uint16_t seq_id, + SerialCommProtoPacket& out_reply, + uint32_t timeout_ms + ); + + + /** + * @brief Resolve transaction reply + * @param packet Reply packet + * @return Result code + */ + errCode resolve_transaction( + const SerialCommProtoPacket& packet + ); + + + private: + + /** + * @brief Find transaction by seq_id + */ + Transaction* find_transaction( uint16_t seq_id ); + + + /** + * @brief Destroy transaction + */ + void destroy_transaction( uint16_t seq_id ); + + + public: + /** + * @brief Destructor + */ + virtual ~SerialCommTransactionManager(); + +}; \ No newline at end of file diff --git a/middleware/service/serial_comm_service.cpp b/middleware/service/serial_comm_service.cpp new file mode 100644 index 0000000..a867fb8 --- /dev/null +++ b/middleware/service/serial_comm_service.cpp @@ -0,0 +1,110 @@ +/** + * @file serial_comm_service.h + * @brief Generic Service abstraction for SerialComm middleware + * @details Provides a ROS-like request/reply abstraction over the + * SerialComm protocol. + * + * Responsibilities: + * - Typed request/reply callbacks + * - Automatic serialization abstraction + * - Automatic response packet generation + * - Service callback encapsulation + * + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#pragma once + +#include "serial_comm_service.h" + + +using namespace SerialCommResult_Codes; + + +bool SerialCommService::execute( const Req& request, Res& response ) { + if ( !this->initialized_ ) { + return false; + } + if ( this->callback_ == nullptr ) { + return false; + } + return this->callback_( request, response ); +} + + +errCode SerialCommService::handle_packet( + const SerialCommProtoPacket& request, + SerialCommProtoPacket& response +) override { + if ( !this->initialized_ ) { + return errCode::ERR_NOT_INITIALIZED; + } + if ( this->callback_ == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + + // Deserialize request + Req request_msg; + bool ret = Serializer::deserialize( + request.payload, + request.header.payload_len, + request_msg + ); + if ( !ret ) { + return errCode::ERR_PARSER; + } + + // Execute callback + Res response_msg; + ret = this->execute( request_msg, response_msg ); + if ( !ret ) { + return errCode::ERR_FAIL; + } + + // Serialize response + size_t serializer_size = 0; + ret = Serializer::serialize( + response_msg, + response.payload, + sizeof(response.payload), + serializer_size + ); + if ( !ret ) { + return errCode::ERR_PARSER; + } + + // Build response packet + SerialCommProtocol::clear_packet( response ); + response.header.seq_id = request.header.seq_id; + response.header.version = SERIAL_COMM_PROTOCOL_VER1; + response.header.command = make_reply( request.header.command ); + response.header.payload_len = static_cast( serializer_size ); + return errCode::OK; +} + + +errCode SerialCommService::init( Command command, service_callback_t callback ) { + if ( callback == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + this->command_ = command; + this->callback_ = callback; + this->initialized_ = true; + return errCode::OK; +} + + +Command SerialCommService::command() const { + return this->command_; +} + + +bool SerialCommService::initialized() const { + return this->initialized_; +} + + +bool SerialCommService::valid() const { + return ( this->callback_ != nullptr ); +} diff --git a/middleware/service/serial_comm_service.h b/middleware/service/serial_comm_service.h new file mode 100644 index 0000000..833ab0d --- /dev/null +++ b/middleware/service/serial_comm_service.h @@ -0,0 +1,149 @@ +/** + * @file serial_comm_service.h + * @brief Generic Service abstraction for SerialComm middleware + * @details Provides a ROS-like request/reply abstraction over the + * SerialComm protocol. + * + * Responsibilities: + * - Typed request/reply callbacks + * - Automatic serialization abstraction + * - Automatic response packet generation + * - Service callback encapsulation + * + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#pragma once + +#include "messages/serial_comm_messages.h" + +#include "core/serial_comm_protocol.h" +#include "core/serial_comm_utils.h" + +#include "serial_comm_service_base.h" +#include "serial_comm_serializer.h" + +#include +#include + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + + +/** + * @brief Generic SerialComm Service abstraction + * @tparam Req Request type + * @tparam Res Response type + */ +template +class SerialCommService: public IServiceBase { + public: + + /** + * @brief Service callback type + * @param request Typed request object + * @param response Typed response object + * @return true Service executed successfully + * @return false Service execution failed + */ + using service_callback_t = + bool (*)( const Req& request, Res& response ); + + + public: + + /** + * @brief Constructor + */ + SerialCommService() = default; + + + private: + + /** + * @brief Registered command ID + */ + Command command_ = static_cast(0); + + /** + * @brief Service callback + */ + service_callback_t callback_ = nullptr; + bool initialized_ = false; + + + private: + /** + * @brief Execute service callback + * @param request Request object + * @param response Response object + * @return true Callback executed successfully + * @return false Callback execution failed + */ + bool execute( const Req& request, Res& response ); + + + public: + + /** + * @brief Initialize service + * @param command Service command ID + * @param callback User callback + * @return Result code + */ + errCode init( Command command, service_callback_t callback ) { + if ( callback == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + this->command_ = command; + this->callback_ = callback; + this->initialized_ = true; + return errCode::OK; + } + + public: + + /** + * @brief Get command ID + * @return Command ID + */ + Command command() const override; + + + /** + * @brief Handle incoming service request packet, execute + * callback and generate response packet + * @param request Incoming request packet + * @param response Output response packet to fill + * @return Result code + */ + errCode handle_packet( + const SerialCommProtoPacket& request, + SerialCommProtoPacket& response + ) override; + + + /** + * @brief Check if service is initialized + * @return true Service is initialized + * @return false Service is not initialized + */ + bool initialized() const; + + + /** + * @brief Check if callback is valid + * @return true Callback is valid + * @return false Callback is not valid + */ + bool valid() const; + + + public: + + /** + * @brief Destructor + */ + virtual ~SerialCommService() = default; +}; \ No newline at end of file diff --git a/middleware/service/serial_comm_service_base.h b/middleware/service/serial_comm_service_base.h new file mode 100644 index 0000000..313b881 --- /dev/null +++ b/middleware/service/serial_comm_service_base.h @@ -0,0 +1,40 @@ +/** + * @file serial_comm_service_base.h + * @brief Base polymorphic interface for middleware services + */ + +#pragma once + +#include "core/serial_comm_protocol.h" +#include "core/serial_comm_utils.h" + +#include + + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + +/** + * @brief Base service interface + */ +class IServiceBase { + public: + virtual ~IServiceBase() = default; + + public: + /** + * @brief Get service command ID + */ + virtual Command command() const = 0; + + /** + * @brief Handle incoming service packet + * @param request Incoming request packet + * @param response Output reply packet + * @return Result code + */ + virtual errCode handle_packet( + const SerialCommProtoPacket& request, + SerialCommProtoPacket& response + ) = 0; +}; \ No newline at end of file diff --git a/middleware/topic/serial_comm_topic.cpp b/middleware/topic/serial_comm_topic.cpp new file mode 100644 index 0000000..3c7ed66 --- /dev/null +++ b/middleware/topic/serial_comm_topic.cpp @@ -0,0 +1,76 @@ +/** + * @file serial_comm_topic.h + * @brief Generic Topic abstraction for SerialComm middleware + * @details Provides a ROS-like publish/subscribe abstraction over the + * SerialComm protocol. + * + * Responsibilities: + * - Typed publish/subscribe abstraction + * - Topic callback encapsulation + * - Lightweight async message delivery + * + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#include "serial_comm_topic.h" + + +void SerialCommTopic::execute( const Msg& msg ) { + if ( !this->initialized_ ) { + return; + } + if ( this->callback_ == nullptr ) { + return; + } + this->callback_(msg); +} + + +errCode SerialCommTopic::handle_packet( const SerialCommProtoPacket& packet ) { + if ( !this->initialized_ ) { + return errCode::ERR_NOT_INITIALIZED; + } + Msg msg; + errCode res = Serializer::deserialize( + packet.payload, + packet.header.payload_len, + msg + ); + if ( res != errCode::OK ) { + return res; + } + this->execute( msg ); + return errCode::OK; +} + + +errCode SerialCommTopic::init( + Command command, + topic_callback_t callback +) { + if ( callback == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + this->command_ = command; + this->callback_ = callback; + this->initialized_ = true; + return errCode::OK; +} + + +Command SerialCommTopic::command() const { + return this->command_; +} + + +bool SerialCommTopic::initialized() const { + return this->initialized_; +} + + +bool SerialCommTopic::valid() const { + return ( this->callback_ != nullptr ); +} + + \ No newline at end of file diff --git a/middleware/topic/serial_comm_topic.h b/middleware/topic/serial_comm_topic.h new file mode 100644 index 0000000..428e58c --- /dev/null +++ b/middleware/topic/serial_comm_topic.h @@ -0,0 +1,127 @@ +/** + * @file serial_comm_topic.h + * @brief Generic Topic abstraction for SerialComm middleware + * @details Provides a ROS-like publish/subscribe abstraction over the + * SerialComm protocol. + * + * Responsibilities: + * - Typed publish/subscribe abstraction + * - Topic callback encapsulation + * - Lightweight async message delivery + * + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#pragma once + +#include "messages/serial_comm_messages.h" + +#include "core/serial_comm_protocol.h" +#include "core/serial_comm_utils.h" + +#include "serial_comm_topic_base.h" +#include "serial_comm_serializer.h" + +#include +#include + + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + + +/** + * @brief Generic Topic abstraction + * @tparam Msg Topic message type + */ +template +class SerialCommTopic : public ITopicBase { + public: + + /** + * @brief Topic callback type + * @param msg Received message + */ + using topic_callback_t = void (*)( const Msg& msg ); + + + private: + + /** + * @brief Topic command ID + */ + Command command_ = static_cast(0); + + /** + * @brief Subscription callback + */ + topic_callback_t callback_ = nullptr; + bool initialized_ = false; + + + private: + /** + * @brief Execute topic callback + * @param msg Received message + */ + void execute( const Msg& msg ); + + + public: + + /** + * @brief Constructor + */ + SerialCommTopic() = default; + + + public: + + /** + * @brief Initialize topic + * @param command Topic command ID + * @param callback Topic callback + * @return Result code + */ + errCode init( Command command, topic_callback_t callback ); + + + /** + * @brief Handle incoming topic packet + * @param packet Incoming packet + * @return Result code + */ + errCode handle_packet( const SerialCommProtoPacket& packet ) override; + + + /** + * @brief Get topic command ID + * @return Command ID + */ + Command command() const override; + + + /** + * @brief Check if topic is initialized + * @return True if initialized + * @return False if not initialized + */ + bool initialized() const; + + + /** + * @brief Check if topic callback is valid + * @return true Callback is valid + * @return false Callback is not valid + */ + bool valid() const; + + + public: + + /** + * @brief Destructor + */ + virtual ~SerialCommTopic() = default; +}; \ No newline at end of file diff --git a/middleware/topic/serial_comm_topic_base.h b/middleware/topic/serial_comm_topic_base.h new file mode 100644 index 0000000..83f6652 --- /dev/null +++ b/middleware/topic/serial_comm_topic_base.h @@ -0,0 +1,39 @@ +/** + * @file serial_comm_topic_base.h + * @brief Base polymorphic interface for middleware topics + */ + +#pragma once + +#include "core/serial_comm_protocol.h" +#include "core/serial_comm_utils.h" + +#include + + +/* To use the errCode and err_to_str easily */ +using namespace SerialCommResult_Codes; + +/** + * @brief Base topic interface + */ +class ITopicBase { + public: + virtual ~ITopicBase() = default; + + public: + + /** + * @brief Get topic command ID + */ + virtual Command command() const = 0; + + /** + * @brief Handle incoming topic packet + * @param packet Incoming packet + * @return Result code + */ + virtual errCode handle_packet( + const SerialCommProtoPacket& packet + ) = 0; +}; \ No newline at end of file diff --git a/src/serial_comm_eventloop.cpp b/serial_comm_eventloop.cpp similarity index 100% rename from src/serial_comm_eventloop.cpp rename to serial_comm_eventloop.cpp diff --git a/src/serial_comm_crc.cpp b/src/serial_comm_crc.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/serial_comm_ringbuff.cpp b/src/serial_comm_ringbuff.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/transport/README.md b/transport/README.md new file mode 100644 index 0000000..a47e92b --- /dev/null +++ b/transport/README.md @@ -0,0 +1,25 @@ +# Transport Layer + +## Hardware abstraction layer + +Responsável por: + +- UART; +- USB CDC; +- TCP; +- BLE; +- SPI; +- RS485. + +Não conhece: + +- packet; +- parser; +- services; +- topics. + +Trabalha apenas com bytes em formato de Stream. + +## TODO + +Implementar um transporte que seja em quadros e não stream como a serial é hoje. diff --git a/src/uart_serial_comm.cpp b/transport/uart_serial_comm.cpp similarity index 100% rename from src/uart_serial_comm.cpp rename to transport/uart_serial_comm.cpp diff --git a/include/uart_serial_comm.h b/transport/uart_serial_comm.h similarity index 100% rename from include/uart_serial_comm.h rename to transport/uart_serial_comm.h From ed9fd1ff86411dde7b2f5caa0c54d094ba250776 Mon Sep 17 00:00:00 2001 From: iOsnaaente Date: Wed, 27 May 2026 18:23:34 -0300 Subject: [PATCH 4/4] feat: v0.2.4 implemented - Working as planned --- .gitignore | 4 +- CMakeLists.txt | 42 ++- Kconfig | 98 ------- Kconfig.projbuild | 219 ++++++++++++++++ core/serial_comm_config.h | 60 ----- idf_component.yml | 247 ++++++++++++++++++ .../core}/serial_comm_dispatcher.h | 10 +- .../serial_comm/core}/serial_comm_parser.h | 4 +- .../serial_comm/core}/serial_comm_protocol.h | 11 +- .../serial_comm/core}/serial_comm_transport.h | 7 +- .../serial_comm/core}/serial_comm_utils.h | 0 .../serial_comm/core}/serial_comm_watchdog.h | 4 +- .../messages}/serial_comm_messages.h | 27 +- .../middleware}/action/serial_comm_action.h | 76 +++++- .../action/serial_comm_action_base.h | 4 +- .../middleware}/serial_comm_manager.h | 228 +++++++++++++--- .../middleware}/serial_comm_serializer.h | 0 .../serial_comm_transaction_manager.h | 23 +- .../middleware}/service/serial_comm_service.h | 75 +++++- .../service/serial_comm_service_base.h | 4 +- .../middleware}/topic/serial_comm_topic.h | 59 ++++- .../topic/serial_comm_topic_base.h | 0 {core => include/serial_comm}/serial_comm.h | 7 +- include/serial_comm/serial_comm_config.h | 196 ++++++++++++++ .../serial_comm/transport}/uart_serial_comm.h | 26 +- middleware/action/serial_comm_action.cpp | 103 -------- middleware/serial_comm_manager_service.cpp | 159 ----------- middleware/serial_comm_manager_topic.cpp | 124 --------- middleware/service/serial_comm_service.cpp | 110 -------- middleware/topic/serial_comm_topic.cpp | 76 ------ serial_comm_eventloop.cpp | 0 {core => src/core}/README.md | 0 {core => src/core}/serial_comm.cpp | 37 ++- {core => src/core}/serial_comm_dispatcher.cpp | 4 +- {core => src/core}/serial_comm_parser.cpp | 13 +- {core => src/core}/serial_comm_protocol.cpp | 4 +- {core => src/core}/serial_comm_watchdog.cpp | 17 +- {middleware => src/middleware}/README.md | 0 .../middleware}/serial_comm_manager.cpp | 8 +- .../serial_comm_manager_action.cpp | 26 -- .../serial_comm_manager_service.cpp | 89 +++++++ src/middleware/serial_comm_manager_topic.cpp | 39 +++ .../serial_comm_transaction_manager.cpp | 18 +- {transport => src/transport}/README.md | 0 .../transport}/uart_serial_comm.cpp | 62 +++-- 45 files changed, 1342 insertions(+), 978 deletions(-) delete mode 100644 Kconfig create mode 100644 Kconfig.projbuild delete mode 100644 core/serial_comm_config.h create mode 100644 idf_component.yml rename {core => include/serial_comm/core}/serial_comm_dispatcher.h (95%) rename {core => include/serial_comm/core}/serial_comm_parser.h (97%) rename {core => include/serial_comm/core}/serial_comm_protocol.h (95%) rename {core => include/serial_comm/core}/serial_comm_transport.h (97%) rename {core => include/serial_comm/core}/serial_comm_utils.h (100%) rename {core => include/serial_comm/core}/serial_comm_watchdog.h (98%) rename {messages => include/serial_comm/messages}/serial_comm_messages.h (69%) rename {middleware => include/serial_comm/middleware}/action/serial_comm_action.h (63%) rename {middleware => include/serial_comm/middleware}/action/serial_comm_action_base.h (88%) rename {middleware => include/serial_comm/middleware}/serial_comm_manager.h (62%) rename {middleware => include/serial_comm/middleware}/serial_comm_serializer.h (100%) rename {middleware => include/serial_comm/middleware}/serial_comm_transaction_manager.h (88%) rename {middleware => include/serial_comm/middleware}/service/serial_comm_service.h (60%) rename {middleware => include/serial_comm/middleware}/service/serial_comm_service_base.h (89%) rename {middleware => include/serial_comm/middleware}/topic/serial_comm_topic.h (61%) rename {middleware => include/serial_comm/middleware}/topic/serial_comm_topic_base.h (100%) rename {core => include/serial_comm}/serial_comm.h (96%) create mode 100644 include/serial_comm/serial_comm_config.h rename {transport => include/serial_comm/transport}/uart_serial_comm.h (82%) delete mode 100644 middleware/action/serial_comm_action.cpp delete mode 100644 middleware/serial_comm_manager_service.cpp delete mode 100644 middleware/serial_comm_manager_topic.cpp delete mode 100644 middleware/service/serial_comm_service.cpp delete mode 100644 middleware/topic/serial_comm_topic.cpp delete mode 100644 serial_comm_eventloop.cpp rename {core => src/core}/README.md (100%) rename {core => src/core}/serial_comm.cpp (89%) rename {core => src/core}/serial_comm_dispatcher.cpp (98%) rename {core => src/core}/serial_comm_parser.cpp (94%) rename {core => src/core}/serial_comm_protocol.cpp (98%) rename {core => src/core}/serial_comm_watchdog.cpp (90%) rename {middleware => src/middleware}/README.md (100%) rename {middleware => src/middleware}/serial_comm_manager.cpp (97%) rename {middleware => src/middleware}/serial_comm_manager_action.cpp (55%) create mode 100644 src/middleware/serial_comm_manager_service.cpp create mode 100644 src/middleware/serial_comm_manager_topic.cpp rename {middleware => src/middleware}/serial_comm_transaction_manager.cpp (88%) rename {transport => src/transport}/README.md (100%) rename {transport => src/transport}/uart_serial_comm.cpp (85%) diff --git a/.gitignore b/.gitignore index 2a41eb3..9a305d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ build/ venv/ -.venv/ \ No newline at end of file +.venv/ + +.vscode/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a65249f..49ace91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,31 +1,29 @@ idf_component_register( SRCS - "serial_comm_eventloop.cpp" - - "core/serial_comm_dispatcher.cpp" - "core/serial_comm_protocol.cpp" - "core/serial_comm_watchdog.cpp" - "core/serial_comm_parser.cpp" - "core/serial_comm.cpp" + "src/core/serial_comm_dispatcher.cpp" + "src/core/serial_comm_protocol.cpp" + "src/core/serial_comm_watchdog.cpp" + "src/core/serial_comm_parser.cpp" + "src/core/serial_comm.cpp" - "middleware/serial_comm_transaction_manager.cpp" - "middleware/serial_comm_manager_service.cpp" - "middleware/serial_comm_manager_action.cpp" - "middleware/serial_comm_manager_topic.cpp" - "middleware/serial_comm_manager.cpp" + "src/middleware/serial_comm_transaction_manager.cpp" + "src/middleware/serial_comm_manager_service.cpp" + "src/middleware/serial_comm_manager_action.cpp" + "src/middleware/serial_comm_manager_topic.cpp" + "src/middleware/serial_comm_manager.cpp" - "middleware/service/serial_comm_service.cpp" - "middleware/action/serial_comm_action.cpp" - "middleware/topic/serial_comm_topic.cpp" - - "transport/uart_serial_comm.cpp" + "src/transport/uart_serial_comm.cpp" INCLUDE_DIRS - "." - "core" - "messages" - "middleware" - "transport" + "include" + "include/serial_comm" + "include/serial_comm/core" + "include/serial_comm/messages" + "include/serial_comm/middleware" + "include/serial_comm/middleware/service" + "include/serial_comm/middleware/action" + "include/serial_comm/middleware/topic" + "include/serial_comm/transport" REQUIRES freertos diff --git a/Kconfig b/Kconfig deleted file mode 100644 index 0189048..0000000 --- a/Kconfig +++ /dev/null @@ -1,98 +0,0 @@ -# -# Serial Communication Middleware Configuration -# - -menu "Serial Communication Middleware" - - config SERIAL_COMM_UART_PORT - int "UART Port" - range 0 2 - default 2 - help - UART peripheral number used by the middleware. - - config SERIAL_COMM_UART_BAUDRATE - int "UART Baudrate" - default 115200 - help - UART communication baudrate. - - config SERIAL_COMM_UART_TX_PIN - int "UART TX GPIO" - range -1 48 - default 17 - help - GPIO used for UART TX. - - config SERIAL_COMM_UART_RX_PIN - int "UART RX GPIO" - range -1 48 - default 16 - help - GPIO used for UART RX. - - config SERIAL_COMM_UART_RTS_PIN - int "UART RTS GPIO" - range -1 48 - default -1 - help - GPIO used for UART RTS. - - config SERIAL_COMM_UART_CTS_PIN - int "UART CTS GPIO" - range -1 48 - default -1 - help - GPIO used for UART CTS. - - config SERIAL_COMM_UART_DE_PIN - int "UART RS485 DE GPIO" - range -1 48 - default -1 - help - GPIO used for RS485 Driver Enable. - - config SERIAL_COMM_UART_RX_BUFFER_SIZE - int "UART RX Buffer Size" - default 1024 - help - UART RX driver buffer size. - - config SERIAL_COMM_UART_TX_BUFFER_SIZE - int "UART TX Buffer Size" - default 1024 - help - UART TX driver buffer size. - - config SERIAL_COMM_UART_EVENT_QUEUE_SIZE - int "UART Event Queue Size" - default 32 - help - UART event queue size. - - config SERIAL_COMM_UART_TASK_STACK_SIZE - int "UART Task Stack Size" - default 4096 - help - Stack size for UART event task. - - config SERIAL_COMM_UART_TASK_PRIORITY - int "UART Task Priority" - default 5 - help - FreeRTOS priority for UART task. - - config SERIAL_COMM_UART_TASK_CORE - int "UART Task Core" - range 0 1 - default 1 - help - CPU core affinity for UART task. - - config SERIAL_COMM_ENABLE_HALF_DUPLEX - bool "Enable Half Duplex Support" - default n - help - Enables RS485 half duplex mode. - -endmenu \ No newline at end of file diff --git a/Kconfig.projbuild b/Kconfig.projbuild new file mode 100644 index 0000000..5b3ab47 --- /dev/null +++ b/Kconfig.projbuild @@ -0,0 +1,219 @@ +menu "SerialComm Middleware" + + menu "Transport Configuration" + + config SERIAL_COMM_UART_PORT + int "UART Port" + range 0 2 + default 2 + + config SERIAL_COMM_UART_BAUDRATE + int "UART Baudrate" + range 9600 1000000 + default 921600 + + config SERIAL_COMM_UART_TX_PIN + int "UART TX GPIO" + range -1 48 + default 17 + + config SERIAL_COMM_UART_RX_PIN + int "UART RX GPIO" + range -1 48 + default 16 + + config SERIAL_COMM_UART_RTS_PIN + int "UART RTS GPIO" + range -1 48 + default -1 + + config SERIAL_COMM_UART_CTS_PIN + int "UART CTS GPIO" + range -1 48 + default -1 + + config SERIAL_COMM_UART_DE_PIN + int "UART RS485 DE GPIO" + range -1 48 + default -1 + + endmenu + + + menu "UART Driver" + + config SERIAL_COMM_UART_RX_BUFFER_SIZE + int "RX Buffer Size" + default 2048 + + config SERIAL_COMM_UART_TX_BUFFER_SIZE + int "TX Buffer Size" + default 2048 + + config SERIAL_COMM_UART_EVENT_QUEUE_SIZE + int "UART Event Queue Size" + default 32 + + endmenu + + + menu "Protocol" + + config SERIAL_COMM_MAX_PAYLOAD_SIZE + int "Maximum Payload Size" + default 512 + + config SERIAL_COMM_ENABLE_CRC + bool "Enable CRC16" + default y + + config SERIAL_COMM_ENABLE_SEQ_ID + bool "Enable Sequence ID" + default y + + endmenu + + + menu "Parser" + + config SERIAL_COMM_ENABLE_INTERBYTE_TIMEOUT + bool "Enable Inter-byte Timeout" + default y + + choice SERIAL_COMM_INTERBYTE_TIMEOUT_MODE + prompt "Inter-byte Timeout Mode" + default SERIAL_COMM_INTERBYTE_TIMEOUT_DYNAMIC + help + Select how the inter-byte timeout is calculated. + config SERIAL_COMM_INTERBYTE_TIMEOUT_FIXED + bool "Fixed Timeout" + config SERIAL_COMM_INTERBYTE_TIMEOUT_DYNAMIC + bool "Character Time Based" + + endchoice + + + config SERIAL_COMM_INTERBYTE_TIMEOUT_US + int "Fixed Inter-byte Timeout (us)" + default 1000 + depends on SERIAL_COMM_INTERBYTE_TIMEOUT_FIXED + help + Fixed timeout value in microseconds (us). + + + config SERIAL_COMM_INTERBYTE_TIMEOUT_CHARS + int "Character Times" + range 2 64 + default 5 + depends on SERIAL_COMM_INTERBYTE_TIMEOUT_DYNAMIC + help + Number of UART character times used to compute + the inter-byte timeout dynamically based on baudrate. + endmenu + + + menu "Dispatcher" + + config SERIAL_COMM_DISPATCHER_QUEUE_SIZE + int "Dispatcher Queue Size" + default 32 + + config SERIAL_COMM_DISPATCHER_TASK_STACK_SIZE + int "Dispatcher Task Stack Size" + range 2048 16384 + default 8192 + + config SERIAL_COMM_DISPATCHER_TASK_PRIORITY + int "Dispatcher Task Priority" + range 2 10 + default 5 + + endmenu + + + menu "Transactions" + + config SERIAL_COMM_MAX_PENDING_TRANSACTIONS + int "Maximum Pending Transactions" + range 1 32 + default 16 + + config SERIAL_COMM_TRANSACTION_TIMEOUT_MS + int "Default Transaction Timeout in ms" + default 1000 + + endmenu + + + menu "Middleware Features" + + config SERIAL_COMM_ENABLE_SERVICES + bool "Enable Services" + default y + + config SERIAL_COMM_ENABLE_TOPICS + bool "Enable Topics" + default y + + config SERIAL_COMM_ENABLE_ACTIONS + bool "Enable Actions" + default n + + if SERIAL_COMM_ENABLE_SERVICES + config SERIAL_COMM_MAX_SERVICES + int "Maximum Services" + range 1 64 + default 16 + help + Maximum number of registered service servers. + endif + + if SERIAL_COMM_ENABLE_TOPICS + config SERIAL_COMM_MAX_SUBSCRIPTIONS + int "Maximum Topic Subscriptions" + range 1 64 + default 16 + help + Maximum number of registered topic subscribers. + + config SERIAL_COMM_MAX_PUBLISHERS + int "Maximum Topic Publishers" + range 1 64 + default 16 + help + Maximum number of registered topic publishers. + endif + + if SERIAL_COMM_ENABLE_ACTIONS + config SERIAL_COMM_MAX_ACTIONS + int "Maximum Actions" + range 1 32 + default 8 + help + Maximum number of registered action servers. + endif + + endmenu + + + menu "Tasks" + + config SERIAL_COMM_UART_TASK_STACK_SIZE + int "UART Task Stack Size" + default 4096 + + config SERIAL_COMM_UART_TASK_PRIORITY + int "UART Task Priority" + range 1 10 + default 5 + + config SERIAL_COMM_UART_TASK_CORE + int "UART Task Core" + range -1 1 + default -1 + help + CPU core affinity for the UART task. Set to -1 for no affinity. + + endmenu + +endmenu \ No newline at end of file diff --git a/core/serial_comm_config.h b/core/serial_comm_config.h deleted file mode 100644 index bf2504c..0000000 --- a/core/serial_comm_config.h +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @file serial_comm_config.hpp - * @brief Serial communication middleware configuration from menuconfig - * - * @note Please dont modify this file directly, as it is generated from - * the Kconfig system. - * - * @note If you want to change any configuration, please do it through - * the menuconfig interface. To do that, run: - * `idf.py menuconfig` - * and navigate to the Serial Communication - */ - -#pragma once - -#include "sdkconfig.h" - -/* ============================================================================ - * UART HARDWARE CONFIGURATION - * ==========================================================================*/ - -#define SERIAL_COMM_UART_PORT CONFIG_SERIAL_COMM_UART_PORT - -#define SERIAL_COMM_UART_BAUDRATE CONFIG_SERIAL_COMM_UART_BAUDRATE - -#define SERIAL_COMM_UART_TX_PIN CONFIG_SERIAL_COMM_UART_TX_PIN - -#define SERIAL_COMM_UART_RX_PIN CONFIG_SERIAL_COMM_UART_RX_PIN - -#define SERIAL_COMM_UART_RTS_PIN CONFIG_SERIAL_COMM_UART_RTS_PIN - -#define SERIAL_COMM_UART_CTS_PIN CONFIG_SERIAL_COMM_UART_CTS_PIN - -#define SERIAL_COMM_UART_DE_PIN CONFIG_SERIAL_COMM_UART_DE_PIN - -/* ============================================================================ - * UART DRIVER CONFIGURATION - * ==========================================================================*/ - -#define SERIAL_COMM_UART_RX_BUFFER_SIZE CONFIG_SERIAL_COMM_UART_RX_BUFFER_SIZE - -#define SERIAL_COMM_UART_TX_BUFFER_SIZE CONFIG_SERIAL_COMM_UART_TX_BUFFER_SIZE - -#define SERIAL_COMM_UART_QUEUE_SIZE CONFIG_SERIAL_COMM_UART_EVENT_QUEUE_SIZE - -/* ============================================================================ - * TASK CONFIGURATION - * ==========================================================================*/ - -#define SERIAL_COMM_UART_TASK_STACK CONFIG_SERIAL_COMM_UART_TASK_STACK_SIZE - -#define SERIAL_COMM_UART_TASK_PRIORITY CONFIG_SERIAL_COMM_UART_TASK_PRIORITY - -#define SERIAL_COMM_UART_TASK_CORE CONFIG_SERIAL_COMM_UART_TASK_CORE - -/* ============================================================================ - * HALF DUPLEX - * ==========================================================================*/ - -#define SERIAL_COMM_HALF_DUPLEX_ENABLED CONFIG_SERIAL_COMM_ENABLE_HALF_DUPLEX \ No newline at end of file diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 0000000..e18b03b --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,247 @@ +version: "0.2.4-beta" + +description: > + Serial communication middleware for ESP-IDF with + services, topics, actions and transaction support. + + A lightweight, extensible middleware component for ESP-IDF that provides + a structured communication framework inspired by ROS2 and micro-ROS + semantics. + +url: "https://github.com/iOsnaaente/SerialComm" + +dependencies: + idf: ">=5.0" + + + +milestones: + - "Migration from callback-based transport to semantic middleware" + - "Protocol V2 introduction" + - "Middleware ROS-inspired abstraction" + - "Transport-independent architecture" + + + +planned: + + - version: "0.2.5" + description: > + Create an Abstration Layer where the user can define their own + middleware behavior based on a predefined ROS-like API structure. + It will allow users to define the structure of the packet of requests + and reply to automatically correlate the request and reply callbacks. + + features: + - Middleware abstraction layer + - ROS-like service/topic/action API + - Generic serialization API + + + - version: "0.3.x" + description: > + Alpha release with initial Wi-Fi transport support and middleware + abstraction layer. + + features: + - Wi-Fi transport support + - Bluetooth transport support + - ROS-like service/topic/action API + - Service discovery mechanism + - Middleware security features + - Enhanced logging and diagnostics + + breakpoints: + - "Added Wi-Fi transport implementation" + - "Introduced middleware abstraction layer" + - "Implemented ROS-like communication semantics" + - "Added generic serialization API" + + +roadmap: + + - version: "0.2.4" + description: > + Refactored middleware configuration system and integrated + advanced menuconfig support and reorganize the directory structure. + + features: + - Kconfig integration + - Config namespace migration + - Runtime feature toggles + - Dispatcher configuration + - Middleware feature flags + - Logging configuration + - Directory structure reorganization + - Add component documentation and roadmap + + breakpoints: + - "Migrated hardcoded values into menuconfig" + - "Separated public/internal configuration" + - "Added configurable middleware features" + - "Added dispatcher/task tunables" + - "Improved component modularity" + + + - version: "0.2.3" + description: > + Added transaction manager and synchronous request/reply service + support. + + features: + - Transaction manager + - Blocking service calls + - Reply synchronization + - Timeout handling + - Pending transaction tracking + + breakpoints: + - "Implemented pending transaction table" + - "Added service request blocking API" + - "Added timeout-based transaction cleanup" + - "Integrated transaction manager into middleware" + + + - version: "0.2.2" + description: > + Introduced middleware abstraction layer inspired by ROS2 and + micro-ROS communication semantics. + + features: + - SerialCommManager + - Service abstraction + - Topic abstraction + - Action abstraction + - Generic serializer API + - Middleware endpoint registration + + breakpoints: + - "Separated transport/core/middleware architecture" + - "Introduced ROS-like services/topics/actions" + - "Implemented typed callback abstraction" + - "Added middleware serialization layer" + - "Introduced semantic middleware API" + + + - version: "0.2.1" + description: > + Added Protocol V2 fields that support sequence identifiers and + semantic request/reply abstraction. + + design decisions: + - Added 16-bit SEQ_ID field to packet structure for transaction + correlation was demonstrate be an important feature for supportting + more complex communication patterns and to enable the implementation + of a transaction manager in the middleware layer, which is a key + component for supporting more complex communication patterns like + synchronous service calls and multi-packet transactions + - The Seq_id and the request/reply bit in the command field was + planned to be present in the protocol version 2, with the source + and destination fields. + + features: + - Some Protocol V2 fields of packet structure + - SEQ_ID support + - Request/reply command bit + - Transaction correlation + - Multiple pending transactions + - Reply packet helpers + + breakpoints: + - "Added 16-bit sequence identifier" + - "Implemented request/reply MSB semantic in command field" + - "Refactored protocol structures" + - "Updated parser for SEQ_ID support" + - "Added transaction matching architecture" + - "Updated Python client for V2 compatibility" + + troubleshooting: + - issue: "Protocol incompatibility with V1 clients" + solution: > + V2 packets now include SEQ_ID field and request/reply bit, that + forces a new implementation of the client to support the new + protocol version. + + + - version: "0.1.2" + description: > + Added dispatcher layer, asynchronous packet processing and + queue-based RX architecture. + + features: + - SerialCommDispatcher + - Async RX queue processing + - Dispatcher task abstraction + - Packet enqueue/dequeue + - Command callback registry + - Multi-packet parsing support + + breakpoints: + - "Separated parser from callback execution" + - "Migrated RX flow to dispatcher task" + - "Added queue-based packet routing" + - "Implemented support for multiple packets per RX buffer" + - "Improved middleware concurrency architecture" + + troubleshooting: + - issue: "Lost packets under high RX throughput" + solution: > + Increase dispatcher queue size and ensure callbacks do not + block dispatcher execution. + + - issue: "Parser only processing first packet" + solution: > + Refactor parse_buffer() to continue parsing after valid + packet extraction and create the parse_next_packet() helper + function that supports incremental parsing. + It solution also add a feature to support multiple packets per + RX buffer, which is a common scenario in a non stream-based + transport like Wi-Fi and other kinds of networks. + + + - version: "0.1.1" + description: > + Added inter-byte timeout watchdog support and parser stream + recovery mechanisms. + + features: + - Inter-byte timeout watchdog + - Parser reset recovery + - Dynamic timeout configuration + - UART timeout helpers + - Parser mutex protection + + breakpoints: + - "Integrated GPTimer-based watchdog abstraction" + - "Moved parser timeout logic from parser to middleware layer" + - "Added parser synchronization recovery" + - "Identified watchdog false-positive resets" + - "Adjusted timeout strategy for USB/UART scheduling jitter" + - "Changed the abstraction architecture for the whole protocol stack" + + troubleshooting: + - issue: "Parser timeout reset spam" + solution: > + Reset parser only when packet assembly is in progress. + + + - version: "0.1.0" + description: > + Initial release with UART transport layer, protocol framing, + CRC16 CCITT validation and incremental parser support. + + features: + - UART transport abstraction + - Protocol V1 implementation + - Incremental byte stream parser + - CRC16 CCITT packet validation + - Packet encoding/decoding + - RX callback registration + - Basic middleware lifecycle + + breakpoints: + - "Defined transport abstraction using ISerialCommTransport" + - "Implemented incremental parser state machine" + - "Added packet framing and CRC16 CCITT validation" + - "Added FreeRTOS-safe TX mutex protection" + - First public release \ No newline at end of file diff --git a/core/serial_comm_dispatcher.h b/include/serial_comm/core/serial_comm_dispatcher.h similarity index 95% rename from core/serial_comm_dispatcher.h rename to include/serial_comm/core/serial_comm_dispatcher.h index c389716..2eb32f9 100644 --- a/core/serial_comm_dispatcher.h +++ b/include/serial_comm/core/serial_comm_dispatcher.h @@ -25,6 +25,7 @@ #pragma once +#include "serial_comm_config.h" #include "serial_comm_protocol.h" #include "serial_comm_utils.h" @@ -53,11 +54,14 @@ class SerialCommDispatcher { */ struct Config { /* RX packet queue size */ - uint32_t rx_queue_len = 16; + uint32_t rx_queue_len = + SerialCommConfig::DISPATCHER_QUEUE_SIZE; /* Dispatcher task stack size */ - uint32_t task_stack_size = 4096; + uint32_t task_stack_size = + SerialCommConfig::DISPATCHER_TASK_STACK_SIZE; /* Dispatcher task priority */ - UBaseType_t task_priority = 5; + UBaseType_t task_priority = + SerialCommConfig::DISPATCHER_TASK_PRIORITY; /* Dispatcher task name */ const char* task_name = "SerialCommDispatcher"; }; diff --git a/core/serial_comm_parser.h b/include/serial_comm/core/serial_comm_parser.h similarity index 97% rename from core/serial_comm_parser.h rename to include/serial_comm/core/serial_comm_parser.h index 63ea48f..93edeae 100644 --- a/core/serial_comm_parser.h +++ b/include/serial_comm/core/serial_comm_parser.h @@ -15,8 +15,8 @@ #pragma once -#include "serial_comm_protocol.h" -#include "serial_comm_utils.h" +#include "serial_comm/core/serial_comm_protocol.h" +#include "serial_comm/core/serial_comm_utils.h" #include #include diff --git a/core/serial_comm_protocol.h b/include/serial_comm/core/serial_comm_protocol.h similarity index 95% rename from core/serial_comm_protocol.h rename to include/serial_comm/core/serial_comm_protocol.h index b468801..85c190e 100644 --- a/core/serial_comm_protocol.h +++ b/include/serial_comm/core/serial_comm_protocol.h @@ -11,6 +11,7 @@ #pragma once +#include "serial_comm_config.h" #include "serial_comm_messages.h" #include "serial_comm_utils.h" @@ -49,8 +50,8 @@ constexpr uint8_t SERIAL_COMM_PROTOCOL_VER2 = 0x02; /** * @brief Maximum payload size */ -constexpr size_t SERIAL_COMM_MAX_PAYLOAD_V1 = 512; -constexpr size_t SERIAL_COMM_MAX_PAYLOAD_V2 = 1024; +constexpr size_t SERIAL_COMM_MAX_PAYLOAD = + SerialCommConfig::MAX_PAYLOAD_SIZE; /** @@ -69,7 +70,7 @@ constexpr size_t SERIAL_COMM_MAX_PACKET_SIZE_V1 = \ SERIAL_COMM_PROTOCOL_VER_SIZE + \ SERIAL_COMM_COMMAND_SIZE + \ SERIAL_COMM_LENGTH_SIZE + \ - SERIAL_COMM_MAX_PAYLOAD_V1 + \ + SERIAL_COMM_MAX_PAYLOAD + \ SERIAL_COMM_CRC_SIZE; @@ -95,7 +96,7 @@ constexpr size_t SERIAL_COMM_MAX_PACKET_SIZE_V2 = \ SERIAL_COMM_FLAG_SIZE + \ SERIAL_COMM_COMMAND_SIZE + \ SERIAL_COMM_LENGTH_SIZE + \ - SERIAL_COMM_MAX_PAYLOAD_V2 + \ + SERIAL_COMM_MAX_PAYLOAD + \ SERIAL_COMM_CRC_SIZE; @@ -130,7 +131,7 @@ struct SerialCommProtoHeader { */ struct SerialCommProtoPacket { SerialCommProtoHeader header; - uint8_t payload[SERIAL_COMM_MAX_PAYLOAD_V2 ] = { 0 }; + uint8_t payload[SERIAL_COMM_MAX_PAYLOAD ] = { 0 }; uint16_t crc = 0; }; diff --git a/core/serial_comm_transport.h b/include/serial_comm/core/serial_comm_transport.h similarity index 97% rename from core/serial_comm_transport.h rename to include/serial_comm/core/serial_comm_transport.h index 7223c66..501c770 100644 --- a/core/serial_comm_transport.h +++ b/include/serial_comm/core/serial_comm_transport.h @@ -12,6 +12,7 @@ #pragma once +#include "serial_comm_config.h" #include "serial_comm_utils.h" #include @@ -47,9 +48,9 @@ class ISerialCommTransport{ /* TRANSPORT CONFIG */ struct Config{ - uint32_t baudrate = 115200; - size_t rx_buffer_size = 1024; - size_t tx_buffer_size = 1024; + uint32_t baudrate = SerialCommConfig::UART_BAUDRATE; + size_t rx_buffer_size = SerialCommConfig::UART_RX_BUFFER_SIZE; + size_t tx_buffer_size = SerialCommConfig::UART_TX_BUFFER_SIZE; bool auto_interbyte_timeout = true; float interbyte_chars = 3.5f; uint32_t timeout_ms = 10; diff --git a/core/serial_comm_utils.h b/include/serial_comm/core/serial_comm_utils.h similarity index 100% rename from core/serial_comm_utils.h rename to include/serial_comm/core/serial_comm_utils.h diff --git a/core/serial_comm_watchdog.h b/include/serial_comm/core/serial_comm_watchdog.h similarity index 98% rename from core/serial_comm_watchdog.h rename to include/serial_comm/core/serial_comm_watchdog.h index ca85b12..334bac2 100644 --- a/core/serial_comm_watchdog.h +++ b/include/serial_comm/core/serial_comm_watchdog.h @@ -19,8 +19,6 @@ #include -using namespace SerialCommUtils; - /** * @brief Serial communication middleware watchdog timer * @details This class implements a watchdog timer using the ESP32's general @@ -89,7 +87,7 @@ class SerialCommWatchdogTimer { */ SerialCommWatchdogTimer( const char* task_name = "SerialCommWatchdog", - uint64_t timeout_us = uart_interbyte_timeout_us(SERIAL_COMM_UART_BAUDRATE), + uint64_t timeout_us = uart_interbyte_timeout_us(SerialCommConfig::UART_BAUDRATE), Callback callback = nullptr, uint32_t stack_size = 1024*4, UBaseType_t priority = 5 diff --git a/messages/serial_comm_messages.h b/include/serial_comm/messages/serial_comm_messages.h similarity index 69% rename from messages/serial_comm_messages.h rename to include/serial_comm/messages/serial_comm_messages.h index 98bc0bd..b94ac00 100644 --- a/messages/serial_comm_messages.h +++ b/include/serial_comm/messages/serial_comm_messages.h @@ -18,6 +18,7 @@ enum class SerialCommCommand : uint8_t { READ = 0x01, WRITE = 0x02, PING = 0x03, + STATUS_TOPIC = 0x06, READ_UTILITY = 0x04, WRITE_UTILITY = 0x05, }; @@ -29,25 +30,21 @@ enum class SerialCommCommand : uint8_t { * @param[in] cmd SerialCommCommand enum * @return const char* SerialCommCommand string */ -const char* serial_command_to_str(SerialCommCommand cmd){ +static inline const char* serial_command_to_str(SerialCommCommand cmd){ switch (cmd) { - case SerialCommCommand::UNDEFINED: - return "UNDEFINED"; - case SerialCommCommand::READ: - return "READ"; - case SerialCommCommand::WRITE: - return "WRITE"; - case SerialCommCommand::PING: - return "PING"; - case SerialCommCommand::READ_UTILITY: - return "READ_UTILITY"; - case SerialCommCommand::WRITE_UTILITY: - return "WRITE_UTILITY"; - default: - return "UNKNOWN_COMMAND"; + case SerialCommCommand::UNDEFINED: return "UNDEFINED"; + case SerialCommCommand::READ: return "READ"; + case SerialCommCommand::WRITE: return "WRITE"; + case SerialCommCommand::PING: return "PING"; + case SerialCommCommand::STATUS_TOPIC: return "STATUS_TOPIC"; + case SerialCommCommand::READ_UTILITY: return "READ_UTILITY"; + case SerialCommCommand::WRITE_UTILITY: return "WRITE_UTILITY"; + default: return "UNKNOWN_COMMAND"; } } +using Command = SerialCommCommand; + /** * @brief Command flag bit mask */ diff --git a/middleware/action/serial_comm_action.h b/include/serial_comm/middleware/action/serial_comm_action.h similarity index 63% rename from middleware/action/serial_comm_action.h rename to include/serial_comm/middleware/action/serial_comm_action.h index 9dcd0ff..e4413b8 100644 --- a/middleware/action/serial_comm_action.h +++ b/include/serial_comm/middleware/action/serial_comm_action.h @@ -20,13 +20,13 @@ #pragma once -#include "messages/serial_comm_messages.h" +#include "serial_comm/messages/serial_comm_messages.h" -#include "core/serial_comm_protocol.h" -#include "core/serial_comm_utils.h" +#include "serial_comm/core/serial_comm_protocol.h" +#include "serial_comm/core/serial_comm_utils.h" -#include "serial_comm_action_base.h" -#include "serial_comm_serializer.h" +#include "serial_comm/middleware/action/serial_comm_action_base.h" +#include "serial_comm/middleware/serial_comm_serializer.h" #include #include @@ -102,7 +102,16 @@ class SerialCommAction : public IActionBase { Command command, goal_callback_t goal_cb, feedback_callback_t feedback_cb = nullptr - ); + ) { + if ( goal_cb == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + this->command_ = command; + this->goal_callback_ = goal_cb; + this->feedback_callback_ = feedback_cb; + this->initialized_ = true; + return errCode::OK; + } private: @@ -113,7 +122,15 @@ class SerialCommAction : public IActionBase { * @return true Goal executed successfully * @return false Goal execution failed */ - bool execute_goal( const Goal& goal, Result& result ); + bool execute_goal( const Goal& goal, Result& result ) { + if ( !this->initialized_ ) { + return false; + } + if ( this->goal_callback_ == nullptr ) { + return false; + } + return this->goal_callback_( goal, result ); + } public: @@ -121,13 +138,23 @@ class SerialCommAction : public IActionBase { * @brief Publish feedback * @param feedback Feedback object */ - void publish_feedback( const Feedback& feedback ); + void publish_feedback( const Feedback& feedback ) { + if ( !this->initialized_ ) { + return; + } + if ( this->feedback_callback_ == nullptr ) { + return; + } + this->feedback_callback_( feedback ); + } /** * @brief Get action command ID */ - Command command() const override; + Command command() const override { + return this->command_; + } /** @@ -137,7 +164,28 @@ class SerialCommAction : public IActionBase { */ errCode handle_packet( const SerialCommProtoPacket& packet - ) override; + ) override { + if ( !this->initialized_ ) { + return errCode::ERR_NOT_INITIALIZED; + } + Goal goal; + bool ret = Serializer::deserialize( + packet.payload, + packet.header.payload_len, + goal + ); + if ( !ret ) { + return errCode::ERR_PARSER; + } + + Result result; + ret = this->execute_goal( goal, result ); + if ( !ret ) { + return errCode::ERR_FAIL; + } + + return errCode::OK; + } /** @@ -145,14 +193,18 @@ class SerialCommAction : public IActionBase { * @return true Action is initialized * @return false Action is not initialized */ - bool initialized() const; + bool initialized() const { + return this->initialized_; + } /** * @brief Check if action is valid * @return true Action is valid * @return false Action is invalid (e.g. missing callbacks) */ - bool valid() const; + bool valid() const { + return ( this->goal_callback_ != nullptr ); + } public: diff --git a/middleware/action/serial_comm_action_base.h b/include/serial_comm/middleware/action/serial_comm_action_base.h similarity index 88% rename from middleware/action/serial_comm_action_base.h rename to include/serial_comm/middleware/action/serial_comm_action_base.h index 8fd95d4..b4a20a9 100644 --- a/middleware/action/serial_comm_action_base.h +++ b/include/serial_comm/middleware/action/serial_comm_action_base.h @@ -5,8 +5,8 @@ #pragma once -#include "core/serial_comm_protocol.h" -#include "core/serial_comm_utils.h" +#include "serial_comm/core/serial_comm_protocol.h" +#include "serial_comm/core/serial_comm_utils.h" #include diff --git a/middleware/serial_comm_manager.h b/include/serial_comm/middleware/serial_comm_manager.h similarity index 62% rename from middleware/serial_comm_manager.h rename to include/serial_comm/middleware/serial_comm_manager.h index 4d4893b..1878223 100644 --- a/middleware/serial_comm_manager.h +++ b/include/serial_comm/middleware/serial_comm_manager.h @@ -20,17 +20,18 @@ #pragma once -#include "core/serial_comm_messages.h" -#include "core/serial_comm_protocol.h" -#include "core/serial_comm_utils.h" -#include "core/serial_comm.h" +#include "serial_comm/messages/serial_comm_messages.h" +#include "serial_comm/serial_comm_config.h" +#include "serial_comm/core/serial_comm_protocol.h" +#include "serial_comm/core/serial_comm_utils.h" +#include "serial_comm/serial_comm.h" -#include "middleware/serial_comm_transaction_manager.h" -#include "middleware/serial_comm_serializer.h" +#include "serial_comm/middleware/serial_comm_transaction_manager.h" +#include "serial_comm/middleware/serial_comm_serializer.h" -#include "middleware/service/serial_comm_service.h" -#include "middleware/action/serial_comm_action.h" -#include "middleware/topic/serial_comm_topic.h" +#include "serial_comm/middleware/service/serial_comm_service.h" +#include "serial_comm/middleware/action/serial_comm_action.h" +#include "serial_comm/middleware/topic/serial_comm_topic.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -51,9 +52,20 @@ using Command = SerialCommCommand; */ class SerialCommManager { private: - static constexpr size_t MAX_SERVICES = 32; - static constexpr size_t MAX_TOPICS = 32; - static constexpr size_t MAX_ACTIONS = 16; + static constexpr size_t MAX_SERVICES = + (SerialCommConfig::MAX_SERVICES > 0) + ? SerialCommConfig::MAX_SERVICES + : 1; + static constexpr size_t MAX_TOPICS = + ((SerialCommConfig::MAX_SUBSCRIPTIONS + + SerialCommConfig::MAX_PUBLISHERS) > 0) + ? (SerialCommConfig::MAX_SUBSCRIPTIONS + + SerialCommConfig::MAX_PUBLISHERS) + : 1; + static constexpr size_t MAX_ACTIONS = + (SerialCommConfig::MAX_ACTIONS > 0) + ? SerialCommConfig::MAX_ACTIONS + : 1; // Service, Topic and Action registries private: @@ -99,8 +111,9 @@ class SerialCommManager { */ struct Config { bool enable_auto_reply = true; - bool enable_transactions = true; - uint32_t service_timeout_ms = 1000; + bool enable_transactions = true; + uint32_t service_timeout_ms = + SerialCommConfig::TRANSACTION_TIMEOUT_MS; bool enable_logs = true; }; @@ -175,8 +188,29 @@ class SerialCommManager { */ template< typename Req, typename Res > errCode create_service( - SerialCommService* service, - ); + SerialCommService* service + ) { + if ( service == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + if ( !service->initialized() ) { + return errCode::ERR_NOT_INITIALIZED; + } + if ( xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ) != pdTRUE ) { + return errCode::ERR_TIMEOUT; + } + for ( size_t i = 0; i < MAX_SERVICES; i++ ) { + if ( !this->services_[i].used ) { + this->services_[i].used = true; + this->services_[i].command = service->command(); + this->services_[i].service = service; + xSemaphoreGive( this->registry_mutex_ ); + return errCode::OK; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return errCode::ERR_NO_MEMORY; + } /** @@ -195,7 +229,63 @@ class SerialCommManager { const Req& request, Res& response, uint32_t timeout_ms = portMAX_DELAY - ); + ) { + uint8_t payload[ SERIAL_COMM_MAX_PAYLOAD ]; + size_t payload_size = 0; + + bool ok = Serializer::serialize( + request, + payload, + sizeof(payload), + payload_size + ); + if ( !ok ) { + return errCode::ERR_FAIL; + } + + uint16_t seq_id = this->allocate_seq_id(); + errCode err = this->transactions_.create_transaction( seq_id ); + if ( err != errCode::OK ) { + return err; + } + + SerialCommProtoPacket request_packet; + err = build_packet( + command, + seq_id, + payload, + payload_size, + request_packet + ); + if ( err != errCode::OK ) { + return err; + } + + err = this->serial_->send( request_packet ); + if ( err != errCode::OK ) { + return err; + } + + SerialCommProtoPacket reply_packet; + err = this->transactions_.wait_reply( + seq_id, + reply_packet, + timeout_ms + ); + if ( err != errCode::OK ) { + return err; + } + + ok = Serializer::deserialize( + reply_packet.payload, + reply_packet.header.payload_len, + response + ); + if ( !ok ) { + return errCode::ERR_FAIL; + } + return errCode::OK; + } // TOPICS @@ -209,7 +299,26 @@ class SerialCommManager { template< typename Msg > errCode create_publisher( SerialCommTopic* publisher - ); + ) { + if ( publisher == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + if ( !publisher->initialized() ) { + return errCode::ERR_NOT_INITIALIZED; + } + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < MAX_TOPICS; i++) { + if ( !this->topics_[i].used ) { + this->topics_[i].used = true; + this->topics_[i].command = publisher->command(); + this->topics_[i].topic = publisher; + xSemaphoreGive( this->registry_mutex_ ); + return errCode::OK; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return errCode::ERR_NO_MEMORY; + } /** @@ -220,7 +329,26 @@ class SerialCommManager { template< typename Msg > errCode create_subscription( SerialCommTopic* subscription - ); + ) { + if ( subscription == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + if ( !subscription->initialized() ) { + return errCode::ERR_NOT_INITIALIZED; + } + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < MAX_TOPICS; i++) { + if ( !this->topics_[i].used ) { + this->topics_[i].used = true; + this->topics_[i].command = subscription->command(); + this->topics_[i].topic = subscription; + xSemaphoreGive( this->registry_mutex_ ); + return errCode::OK; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return errCode::ERR_NO_MEMORY; + } /** @@ -231,7 +359,32 @@ class SerialCommManager { * @return Result code */ template< typename Msg > - errCode publish( Command command, const Msg& msg ); + errCode publish( Command command, const Msg& msg ) { + uint8_t payload[ SERIAL_COMM_MAX_PAYLOAD ]; + size_t payload_size = 0; + bool ok = Serializer::serialize( + msg, + payload, + sizeof(payload), + payload_size + ); + if ( !ok ) { + return errCode::ERR_FAIL; + } + + SerialCommProtoPacket packet; + errCode err = build_packet( + command, + 0, + payload, + payload_size, + packet + ); + if ( err != errCode::OK ) { + return err; + } + return this->serial_->send( packet ); + } // ACTIONS @@ -250,7 +403,26 @@ class SerialCommManager { template< typename Goal, typename Feedback, typename Result > errCode create_action( SerialCommAction* action - ); + ) { + if ( action == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + if ( !action->initialized() ) { + return errCode::ERR_NOT_INITIALIZED; + } + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < MAX_ACTIONS; i++ ) { + if ( !this->actions_[i].used ) { + this->actions_[i].used = true; + this->actions_[i].command = action->command(); + this->actions_[i].action = action; + xSemaphoreGive( this->registry_mutex_ ); + return errCode::OK; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return errCode::ERR_NO_MEMORY; + } // INTERNAL ROUTING @@ -341,20 +513,6 @@ class SerialCommManager { const Res& response ); - /** - * @brief Publish topic message helper - * @tparam Msg Topic message type - * @param command Topic command ID - * @param message Message object to serialize and publish - * @return Result code - */ - template< typename Msg > - errCode publish( - Command command, - const Msg& message - ); - - // HELPERS private: diff --git a/middleware/serial_comm_serializer.h b/include/serial_comm/middleware/serial_comm_serializer.h similarity index 100% rename from middleware/serial_comm_serializer.h rename to include/serial_comm/middleware/serial_comm_serializer.h diff --git a/middleware/serial_comm_transaction_manager.h b/include/serial_comm/middleware/serial_comm_transaction_manager.h similarity index 88% rename from middleware/serial_comm_transaction_manager.h rename to include/serial_comm/middleware/serial_comm_transaction_manager.h index e5897b7..7b7ed2e 100644 --- a/middleware/serial_comm_transaction_manager.h +++ b/include/serial_comm/middleware/serial_comm_transaction_manager.h @@ -14,10 +14,11 @@ #pragma once -#include "messages/serial_comm_messages.h" +#include "serial_comm/messages/serial_comm_messages.h" +#include "serial_comm/serial_comm_config.h" -#include "core/serial_comm_protocol.h" -#include "core/serial_comm_utils.h" +#include "serial_comm/core/serial_comm_protocol.h" +#include "serial_comm/core/serial_comm_utils.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -31,18 +32,16 @@ using namespace SerialCommResult_Codes; -/** - * @brief Maximum simultaneous transactions - */ -#ifndef SERIAL_COMM_MAX_TRANSACTIONS - #define SERIAL_COMM_MAX_TRANSACTIONS 16 -#endif - - /** * @brief Transaction manager */ class SerialCommTransactionManager { + private: + static constexpr size_t MAX_TRANSACTIONS = + (SerialCommConfig::MAX_PENDING_TRANSACTIONS > 0) + ? SerialCommConfig::MAX_PENDING_TRANSACTIONS + : 1; + private: /** * @brief Pending transaction entry @@ -69,7 +68,7 @@ class SerialCommTransactionManager { * In a real implementation, a more efficient data structure * (e.g., hash map) could be used. */ - Transaction transactions_[ SERIAL_COMM_MAX_TRANSACTIONS ]; + Transaction transactions_[ MAX_TRANSACTIONS ]; /** * @brief Internal protection mutex diff --git a/middleware/service/serial_comm_service.h b/include/serial_comm/middleware/service/serial_comm_service.h similarity index 60% rename from middleware/service/serial_comm_service.h rename to include/serial_comm/middleware/service/serial_comm_service.h index 833ab0d..e6f3e45 100644 --- a/middleware/service/serial_comm_service.h +++ b/include/serial_comm/middleware/service/serial_comm_service.h @@ -16,13 +16,13 @@ #pragma once -#include "messages/serial_comm_messages.h" +#include "serial_comm/messages/serial_comm_messages.h" -#include "core/serial_comm_protocol.h" -#include "core/serial_comm_utils.h" +#include "serial_comm/core/serial_comm_protocol.h" +#include "serial_comm/core/serial_comm_utils.h" -#include "serial_comm_service_base.h" -#include "serial_comm_serializer.h" +#include "serial_comm/middleware/service/serial_comm_service_base.h" +#include "serial_comm/middleware/serial_comm_serializer.h" #include #include @@ -81,7 +81,15 @@ class SerialCommService: public IServiceBase { * @return true Callback executed successfully * @return false Callback execution failed */ - bool execute( const Req& request, Res& response ); + bool execute( const Req& request, Res& response ) { + if ( !this->initialized_ ) { + return false; + } + if ( this->callback_ == nullptr ) { + return false; + } + return this->callback_( request, response ); + } public: @@ -108,7 +116,9 @@ class SerialCommService: public IServiceBase { * @brief Get command ID * @return Command ID */ - Command command() const override; + Command command() const override { + return this->command_; + } /** @@ -121,7 +131,48 @@ class SerialCommService: public IServiceBase { errCode handle_packet( const SerialCommProtoPacket& request, SerialCommProtoPacket& response - ) override; + ) override { + if ( !this->initialized_ ) { + return errCode::ERR_NOT_INITIALIZED; + } + if ( this->callback_ == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + + Req request_msg; + bool ret = Serializer::deserialize( + request.payload, + request.header.payload_len, + request_msg + ); + if ( !ret ) { + return errCode::ERR_PARSER; + } + + Res response_msg; + ret = this->execute( request_msg, response_msg ); + if ( !ret ) { + return errCode::ERR_FAIL; + } + + size_t serializer_size = 0; + ret = Serializer::serialize( + response_msg, + response.payload, + sizeof(response.payload), + serializer_size + ); + if ( !ret ) { + return errCode::ERR_PARSER; + } + + SerialCommProtocol::clear_packet( response ); + response.header.seq_id = request.header.seq_id; + response.header.version = SERIAL_COMM_PROTOCOL_VER1; + response.header.command = make_reply( request.header.command ); + response.header.payload_len = static_cast( serializer_size ); + return errCode::OK; + } /** @@ -129,7 +180,9 @@ class SerialCommService: public IServiceBase { * @return true Service is initialized * @return false Service is not initialized */ - bool initialized() const; + bool initialized() const { + return this->initialized_; + } /** @@ -137,7 +190,9 @@ class SerialCommService: public IServiceBase { * @return true Callback is valid * @return false Callback is not valid */ - bool valid() const; + bool valid() const { + return ( this->callback_ != nullptr ); + } public: diff --git a/middleware/service/serial_comm_service_base.h b/include/serial_comm/middleware/service/serial_comm_service_base.h similarity index 89% rename from middleware/service/serial_comm_service_base.h rename to include/serial_comm/middleware/service/serial_comm_service_base.h index 313b881..2f084ed 100644 --- a/middleware/service/serial_comm_service_base.h +++ b/include/serial_comm/middleware/service/serial_comm_service_base.h @@ -5,8 +5,8 @@ #pragma once -#include "core/serial_comm_protocol.h" -#include "core/serial_comm_utils.h" +#include "serial_comm/core/serial_comm_protocol.h" +#include "serial_comm/core/serial_comm_utils.h" #include diff --git a/middleware/topic/serial_comm_topic.h b/include/serial_comm/middleware/topic/serial_comm_topic.h similarity index 61% rename from middleware/topic/serial_comm_topic.h rename to include/serial_comm/middleware/topic/serial_comm_topic.h index 428e58c..c128c58 100644 --- a/middleware/topic/serial_comm_topic.h +++ b/include/serial_comm/middleware/topic/serial_comm_topic.h @@ -15,13 +15,13 @@ #pragma once -#include "messages/serial_comm_messages.h" +#include "serial_comm/messages/serial_comm_messages.h" -#include "core/serial_comm_protocol.h" -#include "core/serial_comm_utils.h" +#include "serial_comm/core/serial_comm_protocol.h" +#include "serial_comm/core/serial_comm_utils.h" -#include "serial_comm_topic_base.h" -#include "serial_comm_serializer.h" +#include "serial_comm/middleware/topic/serial_comm_topic_base.h" +#include "serial_comm/middleware/serial_comm_serializer.h" #include #include @@ -65,7 +65,15 @@ class SerialCommTopic : public ITopicBase { * @brief Execute topic callback * @param msg Received message */ - void execute( const Msg& msg ); + void execute( const Msg& msg ) { + if ( !this->initialized_ ) { + return; + } + if ( this->callback_ == nullptr ) { + return; + } + this->callback_( msg ); + } public: @@ -84,7 +92,15 @@ class SerialCommTopic : public ITopicBase { * @param callback Topic callback * @return Result code */ - errCode init( Command command, topic_callback_t callback ); + errCode init( Command command, topic_callback_t callback ) { + if ( callback == nullptr ) { + return errCode::ERR_NULL_POINTER; + } + this->command_ = command; + this->callback_ = callback; + this->initialized_ = true; + return errCode::OK; + } /** @@ -92,14 +108,31 @@ class SerialCommTopic : public ITopicBase { * @param packet Incoming packet * @return Result code */ - errCode handle_packet( const SerialCommProtoPacket& packet ) override; + errCode handle_packet( const SerialCommProtoPacket& packet ) override { + if ( !this->initialized_ ) { + return errCode::ERR_NOT_INITIALIZED; + } + Msg msg; + bool ret = Serializer::deserialize( + packet.payload, + packet.header.payload_len, + msg + ); + if ( !ret ) { + return errCode::ERR_PARSER; + } + this->execute( msg ); + return errCode::OK; + } /** * @brief Get topic command ID * @return Command ID */ - Command command() const override; + Command command() const override { + return this->command_; + } /** @@ -107,7 +140,9 @@ class SerialCommTopic : public ITopicBase { * @return True if initialized * @return False if not initialized */ - bool initialized() const; + bool initialized() const { + return this->initialized_; + } /** @@ -115,7 +150,9 @@ class SerialCommTopic : public ITopicBase { * @return true Callback is valid * @return false Callback is not valid */ - bool valid() const; + bool valid() const { + return ( this->callback_ != nullptr ); + } public: diff --git a/middleware/topic/serial_comm_topic_base.h b/include/serial_comm/middleware/topic/serial_comm_topic_base.h similarity index 100% rename from middleware/topic/serial_comm_topic_base.h rename to include/serial_comm/middleware/topic/serial_comm_topic_base.h diff --git a/core/serial_comm.h b/include/serial_comm/serial_comm.h similarity index 96% rename from core/serial_comm.h rename to include/serial_comm/serial_comm.h index 911b4ba..27ade92 100644 --- a/core/serial_comm.h +++ b/include/serial_comm/serial_comm.h @@ -24,6 +24,7 @@ #include "serial_comm_protocol.h" #include "serial_comm_parser.h" #include "serial_comm_utils.h" +#include "serial_comm_config.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -62,9 +63,11 @@ class SerialComm { */ struct Config { /* Inter-byte timeout in microseconds (us) */ - uint32_t inter_byte_timeout_us = 1000; + uint32_t inter_byte_timeout_us = + SerialCommConfig::INTERBYTE_TIMEOUT_US; /* Enable timeout watchdog */ - bool enable_inter_byte_timeout = true; + bool enable_inter_byte_timeout = + SerialCommConfig::ENABLE_INTERBYTE_TIMEOUT; }; private: diff --git a/include/serial_comm/serial_comm_config.h b/include/serial_comm/serial_comm_config.h new file mode 100644 index 0000000..e6087a3 --- /dev/null +++ b/include/serial_comm/serial_comm_config.h @@ -0,0 +1,196 @@ +/** + * @file serial_comm_config.hpp + * @brief Serial communication middleware configuration from menuconfig + * @note Please dont modify this file directly, as it is generated from + * the Kconfig system. + * @note If you want to change any configuration, please do it through + * the menuconfig interface. To do that, run: + * `idf.py menuconfig` + * and navigate to the Serial Communication + */ + +#pragma once + +#include "sdkconfig.h" + +#include +#include + + +/** + * @brief Namespace containing all configuration parameters for the + * serial communication middleware, populated from Kconfig + * menuconfig options. + * + * @details This namespace serves as a centralized location for all + * configuration parameters, making it easy to access and maintain. + * + * @example To use the configuration parameters, do: + * using namespace SerialCommConfig; + * int uart_port = UART_PORT; + * + * @example To use the configuration parameters without qualifying the + * namespace, do: + * SerialCommConfig::UART_PORT; + * + */ +namespace SerialCommConfig { + + // TRANSPORT + constexpr int UART_PORT = + CONFIG_SERIAL_COMM_UART_PORT; + + constexpr uint32_t UART_BAUDRATE = + CONFIG_SERIAL_COMM_UART_BAUDRATE; + + constexpr int UART_TX_PIN = + CONFIG_SERIAL_COMM_UART_TX_PIN; + + constexpr int UART_RX_PIN = + CONFIG_SERIAL_COMM_UART_RX_PIN; + + constexpr int UART_RTS_PIN = + CONFIG_SERIAL_COMM_UART_RTS_PIN; + + constexpr int UART_CTS_PIN = + CONFIG_SERIAL_COMM_UART_CTS_PIN; + + constexpr int UART_DE_PIN = + CONFIG_SERIAL_COMM_UART_DE_PIN; + + + // UART DRIVER + constexpr size_t UART_RX_BUFFER_SIZE = + CONFIG_SERIAL_COMM_UART_RX_BUFFER_SIZE; + + constexpr size_t UART_TX_BUFFER_SIZE = + CONFIG_SERIAL_COMM_UART_TX_BUFFER_SIZE; + + constexpr size_t UART_EVENT_QUEUE_SIZE = + CONFIG_SERIAL_COMM_UART_EVENT_QUEUE_SIZE; + + + // PROTOCOL + constexpr size_t MAX_PAYLOAD_SIZE = + CONFIG_SERIAL_COMM_MAX_PAYLOAD_SIZE; + + constexpr bool ENABLE_CRC = + CONFIG_SERIAL_COMM_ENABLE_CRC; + + constexpr bool ENABLE_SEQ_ID = + CONFIG_SERIAL_COMM_ENABLE_SEQ_ID; + + + // PARSER + constexpr bool ENABLE_INTERBYTE_TIMEOUT = + CONFIG_SERIAL_COMM_ENABLE_INTERBYTE_TIMEOUT; + + #if defined(CONFIG_SERIAL_COMM_INTERBYTE_TIMEOUT_DYNAMIC) + constexpr bool INTERBYTE_TIMEOUT_DYNAMIC = true; + #else + constexpr bool INTERBYTE_TIMEOUT_DYNAMIC = false; + #endif + + #if defined(CONFIG_SERIAL_COMM_INTERBYTE_TIMEOUT_CHARS) + constexpr uint32_t INTERBYTE_TIMEOUT_CHARS = + CONFIG_SERIAL_COMM_INTERBYTE_TIMEOUT_CHARS; + #else + constexpr uint32_t INTERBYTE_TIMEOUT_CHARS = 0; + #endif + + #if defined(CONFIG_SERIAL_COMM_INTERBYTE_TIMEOUT_US) + constexpr uint32_t INTERBYTE_TIMEOUT_US = + CONFIG_SERIAL_COMM_INTERBYTE_TIMEOUT_US; + #else + constexpr uint32_t INTERBYTE_TIMEOUT_US = + (INTERBYTE_TIMEOUT_DYNAMIC && UART_BAUDRATE > 0) + ? static_cast( + (1000000ULL * 10ULL * INTERBYTE_TIMEOUT_CHARS) / + static_cast(UART_BAUDRATE) + ) + : 0; + #endif + + + // DISPATCHER + constexpr size_t DISPATCHER_QUEUE_SIZE = + CONFIG_SERIAL_COMM_DISPATCHER_QUEUE_SIZE; + + constexpr size_t DISPATCHER_TASK_STACK_SIZE = + CONFIG_SERIAL_COMM_DISPATCHER_TASK_STACK_SIZE; + + constexpr uint32_t DISPATCHER_TASK_PRIORITY = + CONFIG_SERIAL_COMM_DISPATCHER_TASK_PRIORITY; + + + // TRANSACTIONS + constexpr size_t MAX_PENDING_TRANSACTIONS = + CONFIG_SERIAL_COMM_MAX_PENDING_TRANSACTIONS; + + constexpr uint32_t TRANSACTION_TIMEOUT_MS = + CONFIG_SERIAL_COMM_TRANSACTION_TIMEOUT_MS; + + + // MIDDLEWARE + constexpr bool ENABLE_SERVICES = + CONFIG_SERIAL_COMM_ENABLE_SERVICES; + + constexpr bool ENABLE_TOPICS = + CONFIG_SERIAL_COMM_ENABLE_TOPICS; + + #if defined(CONFIG_SERIAL_COMM_ENABLE_ACTIONS) + constexpr bool ENABLE_ACTIONS = + CONFIG_SERIAL_COMM_ENABLE_ACTIONS; + #else + constexpr bool ENABLE_ACTIONS = false; + #endif + + + constexpr size_t MAX_SERVICES = + #ifdef CONFIG_SERIAL_COMM_MAX_SERVICES + CONFIG_SERIAL_COMM_MAX_SERVICES; + #else + 0; + #endif + + constexpr size_t MAX_SUBSCRIPTIONS = + #ifdef CONFIG_SERIAL_COMM_MAX_SUBSCRIPTIONS + CONFIG_SERIAL_COMM_MAX_SUBSCRIPTIONS; + #else + 0; + #endif + + constexpr size_t MAX_PUBLISHERS = + #ifdef CONFIG_SERIAL_COMM_MAX_PUBLISHERS + CONFIG_SERIAL_COMM_MAX_PUBLISHERS; + #else + 0; + #endif + + constexpr size_t MAX_ACTIONS = + #ifdef CONFIG_SERIAL_COMM_MAX_ACTIONS + CONFIG_SERIAL_COMM_MAX_ACTIONS; + #else + 0; + #endif + + + // TASKS + constexpr size_t UART_TASK_STACK_SIZE = + CONFIG_SERIAL_COMM_UART_TASK_STACK_SIZE; + + constexpr uint32_t UART_TASK_PRIORITY = + CONFIG_SERIAL_COMM_UART_TASK_PRIORITY; + + // Note: If core affinity is enabled, the task will be pinned to the specified core. + constexpr bool UART_USE_TASK_CORE_AFINITY = + #if CONFIG_SERIAL_COMM_UART_TASK_CORE >= 0 + true; + #else + false; + #endif + + constexpr int UART_TASK_CORE = + CONFIG_SERIAL_COMM_UART_TASK_CORE; + +} diff --git a/transport/uart_serial_comm.h b/include/serial_comm/transport/uart_serial_comm.h similarity index 82% rename from transport/uart_serial_comm.h rename to include/serial_comm/transport/uart_serial_comm.h index 375ca0c..8bdf855 100644 --- a/transport/uart_serial_comm.h +++ b/include/serial_comm/transport/uart_serial_comm.h @@ -10,9 +10,9 @@ #pragma once -#include "serial_comm_transport.h" -#include "serial_comm_config.h" -#include "serial_comm_utils.h" +#include "serial_comm/core/serial_comm_transport.h" +#include "serial_comm/serial_comm_config.h" +#include "serial_comm/core/serial_comm_utils.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -33,12 +33,12 @@ class UARTTransport final : public ISerialCommTransport { * @brief ESP32 UART hardware configuration */ struct HardwareConfig { - int uart_port = SERIAL_COMM_UART_PORT; - int tx_pin = SERIAL_COMM_UART_TX_PIN; - int rx_pin = SERIAL_COMM_UART_RX_PIN; - int rts_pin = SERIAL_COMM_UART_RTS_PIN; - int cts_pin = SERIAL_COMM_UART_CTS_PIN; - int de_pin = SERIAL_COMM_UART_DE_PIN; + int uart_port = SerialCommConfig::UART_PORT; + int tx_pin = SerialCommConfig::UART_TX_PIN; + int rx_pin = SerialCommConfig::UART_RX_PIN; + int rts_pin = SerialCommConfig::UART_RTS_PIN; + int cts_pin = SerialCommConfig::UART_CTS_PIN; + int de_pin = SerialCommConfig::UART_DE_PIN; }; @@ -63,9 +63,7 @@ class UARTTransport final : public ISerialCommTransport { private: - static void uart_event_task( - void* args - ); + static void uart_event_task( void* args ); public: @@ -73,9 +71,7 @@ class UARTTransport final : public ISerialCommTransport { * @brief Constructor for UARTTransport * @param hw_cfg Hardware configuration for the UART transport */ - explicit UARTTransport( - const HardwareConfig& hw_cfg - ); + explicit UARTTransport( const HardwareConfig& hw_cfg ); /* Deleted copy and move constructors */ UARTTransport( UARTTransport&& ) = delete; diff --git a/middleware/action/serial_comm_action.cpp b/middleware/action/serial_comm_action.cpp deleted file mode 100644 index fe3d5b8..0000000 --- a/middleware/action/serial_comm_action.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @file serial_comm_action.h - * @brief Generic Action abstraction for SerialComm middleware - * @details Provides a ROS-like action abstraction for long-running - * asynchronous tasks over the SerialComm protocol. - * - * Responsibilities: - * - Goal handling - * - Feedback handling - * - Result handling - * - Action execution abstraction - * - Cancel support (future) - * - * @note Initial implementation only defines the action abstraction. - * Full execution engine and state machine are future work. - * - * @author Bruno Gabriel Flores Sampaio - * @date Created on 26 of May, 2026 - */ - -#include "serial_comm_action.h" - - -errCode SerialCommAction::init( - Command command, - goal_callback_t goal_cb, - feedback_callback_t feedback_cb = nullptr -) { - if ( goal_cb == nullptr ) { - return errCode::ERR_NULL_POINTER; - } - this->command_ = command; - this->goal_callback_ = goal_cb; - this->feedback_callback_ = feedback_cb; - this->initialized_ = true; - return errCode::OK; -} - -bool SerialCommAction::execute_goal( const Goal& goal, Result& result ) { - if ( !this->initialized_ ) { - return false; - } - if ( this->goal_callback_ == nullptr ) { - return false; - } - return this->goal_callback_( goal, result ); -} - -void SerialCommAction::publish_feedback( const Feedback& feedback ) { - if ( !this->initialized_ ) { - return; - } - if ( this->feedback_callback_ == nullptr ) { - return; - } - this->feedback_callback_( feedback ); -} - -Command SerialCommAction::command() const override { - return this->command_; -} - - -errCode SerialCommAction::handle_packet( - const SerialCommProtoPacket& packet -){ - if ( !this->initialized_ ) { - return errCode::ERR_NOT_INITIALIZED; - } - Goal goal; - bool ret = Serializer::deserialize( - packet.payload, - packet.header.payload_len, - goal - ); - if ( !ret ) { - return errCode::ERR_PARSER; - } - Result result; - ret = this->execute_goal( goal, result ); - if ( !ret ) { - return errCode::ERR_FAIL; - } - - /** - * @TODO: Publish result packet - * - feedback stream - * - goal handling - * - cancelation - * - async execution support - */ - - return errCode::OK; -} - - -bool SerialCommAction::initialized() const { - return this->initialized_; -} - -bool SerialCommAction::valid() const { - return ( this->goal_callback_ != nullptr ); -} diff --git a/middleware/serial_comm_manager_service.cpp b/middleware/serial_comm_manager_service.cpp deleted file mode 100644 index 238315e..0000000 --- a/middleware/serial_comm_manager_service.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/** - * @file serial_comm_manager_service.cpp - * @brief Serial communication middleware service handling - * implementation - * @author Bruno Gabriel Flores Sampaio - * @date Created on 26 of May, 2026 - */ - -#include "serial_comm_manager.h" - - -template -errCode SerialCommManager::create_service( - SerialCommService* service -) { - if ( service == nullptr ) { - return errCode::ERR_NULL_POINTER; - } - if ( !service->initialized() ) { - return errCode::ERR_NOT_INITIALIZED; - } - if ( xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ) != pdTRUE ) { - return errCode::ERR_TIMEOUT; - } - for ( size_t i = 0; i < MAX_SERVICES; i++ ) { - if ( !this->services_[i].used ) { - this->services_[i].used = true; - this->services_[i].command = service->command(); - this->services_[i].service = service; - xSemaphoreGive( this->registry_mutex_ ); - return errCode::OK; - } - } - xSemaphoreGive( this->registry_mutex_ ); - return errCode::ERR_NO_MEMORY; -} - - -template -errCode SerialCommManager::call_service( - Command command, - const Req& request, - Res& response, - uint32_t timeout_ms -) { - uint8_t payload[ SERIAL_COMM_MAX_PAYLOAD_V1 ]; - size_t payload_size = 0; - - // SERIALIZE REQUEST - bool ok = Serializer::serialize( - request, - payload, - sizeof(payload), - payload_size - ); - if ( !ok ) { - return errCode::ERR_FAIL; - } - // ALLOCATE SEQ ID - uint16_t seq_id = this->allocate_seq_id(); - - // CREATE TRANSACTION - errCode err = this->transactions_.create_transaction( seq_id ); - if ( err != errCode::OK ) { - return err; - } - - // BUILD REQUEST PACKET - SerialCommProtoPacket request_packet; - err = build_packet( - command, - seq_id, - payload, - payload_size, - request_packet - ); - if ( err != errCode::OK ) { - return err; - } - - // SEND REQUEST - err = this->serial_->send( request_packet ); - if ( err != errCode::OK ) { - return err; - } - - // WAIT REPLY - SerialCommProtoPacket reply_packet; - err = this->transactions_.wait_reply( - seq_id, - reply_packet, - timeout_ms - ); - if ( err != errCode::OK ) { - return err; - } - - // DESERIALIZE RESPONSE - ok = Serializer::deserialize( - reply_packet.payload, - reply_packet.header.payload_len, - response - ); - if ( !ok ) { - return errCode::ERR_FAIL; - } - return errCode::OK; -} - - -void SerialCommManager::handle_service_request( - const SerialCommProtoPacket& packet -) { - ESP_LOGD( - TAG, - "Request packet received: cmd=0x%02X seq=%u len=%u", - static_cast( packet.header.command ), - packet.header.seq_id, - packet.header.payload_len - ); - - /** - * TODO: - * - SERVICE ROUTING - * - TOPIC ROUTING - * - ACTION ROUTING - * --------------------------------------------------------------------- */ -} - - -SerialCommManager::ServiceEntry* SerialCommManager::find_service( - Command command -) { - xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); - for ( size_t i = 0; i < MAX_SERVICES; i++ ) { - if ( - this->services_[i].used && - this->services_[i].command == command - ) { - xSemaphoreGive( this->registry_mutex_ ); - return &this->services_[i]; - } - } - xSemaphoreGive( this->registry_mutex_ ); - return nullptr; -} - - -bool SerialCommManager::is_service_command( Command command ) const { - for ( size_t i = 0; i < MAX_SERVICES; i++ ) { - if ( - this->services_[i].used && - this->services_[i].command == command - ) { - return true; - } - } - return false; -} \ No newline at end of file diff --git a/middleware/serial_comm_manager_topic.cpp b/middleware/serial_comm_manager_topic.cpp deleted file mode 100644 index 8b07f32..0000000 --- a/middleware/serial_comm_manager_topic.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @file serial_comm_manager_topic.cpp - * @brief SerialCommManager topic handling implementation - * @author Bruno Gabriel Flores Sampaio - * @date Created on 26 of May, 2026 - */ - -#include "serial_comm_manager.h" - - -template -errCode SerialCommManager::create_subscription( - SerialCommTopic* subscription -) { - if ( subscription == nullptr ) { - return errCode::ERR_NULL_POINTER; - } - if ( !subscription->initialized() ) { - return errCode::ERR_NOT_INITIALIZED; - } - xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); - - for ( size_t i = 0; i < MAX_TOPICS; i++) { - if ( !this->topics_[i].used ) { - this->topics_[i].used = true; - this->topics_[i].command = subscription->command(); - this->topics_[i].topic = subscription; - xSemaphoreGive( this->registry_mutex_ ); - return errCode::OK; - } - } - xSemaphoreGive( this->registry_mutex_ ); - return errCode::ERR_NO_MEMORY; -} - - -template -errCode SerialCommManager::create_publisher( - SerialCommTopic* publisher -) { - if ( publisher == nullptr ) { - return errCode::ERR_NULL_POINTER; - } - if ( !publisher->initialized() ) { - return errCode::ERR_NOT_INITIALIZED; - } - xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); - for ( size_t i = 0; i < MAX_TOPICS; i++) { - if ( !this->topics_[i].used ) { - this->topics_[i].used = true; - this->topics_[i].command = publisher->command(); - this->topics_[i].topic = publisher; - xSemaphoreGive( this->registry_mutex_ ); - return errCode::OK; - } - } - xSemaphoreGive( this->registry_mutex_ ); - return errCode::ERR_NO_MEMORY; -} - - -template -errCode SerialCommManager::publish( - Command command, - const Msg& msg -) { - uint8_t payload[ SERIAL_COMM_MAX_PAYLOAD_V1 ]; - size_t payload_size = 0; - bool ok = Serializer::serialize( - msg, - payload, - sizeof(payload), - payload_size - ); - if ( !ok ) { - return errCode::ERR_FAIL; - } - - SerialCommProtoPacket packet; - errCode err = build_packet( - command, - 0, - payload, - payload_size, - packet - ); - if ( err != errCode::OK ) { - return err; - } - return this->serial_->send( - packet - ); -} - - -SerialCommManager::TopicEntry* SerialCommManager::find_topic( - Command command -) { - xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); - for ( size_t i = 0; i < MAX_TOPICS; i++ ) { - if ( - this->topics_[i].used && - this->topics_[i].command == command - ) { - xSemaphoreGive( this->registry_mutex_ ); - return &this->topics_[i]; - } - } - xSemaphoreGive( this->registry_mutex_ ); - return nullptr; -} - - -bool SerialCommManager::is_topic_command( Command command ) const { - for ( size_t i = 0; i < MAX_TOPICS; i++ ) { - if ( - this->topics_[i].used && - this->topics_[i].command == command - ) { - return true; - } - } - return false; -} diff --git a/middleware/service/serial_comm_service.cpp b/middleware/service/serial_comm_service.cpp deleted file mode 100644 index a867fb8..0000000 --- a/middleware/service/serial_comm_service.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @file serial_comm_service.h - * @brief Generic Service abstraction for SerialComm middleware - * @details Provides a ROS-like request/reply abstraction over the - * SerialComm protocol. - * - * Responsibilities: - * - Typed request/reply callbacks - * - Automatic serialization abstraction - * - Automatic response packet generation - * - Service callback encapsulation - * - * @author Bruno Gabriel Flores Sampaio - * @date Created on 26 of May, 2026 - */ - -#pragma once - -#include "serial_comm_service.h" - - -using namespace SerialCommResult_Codes; - - -bool SerialCommService::execute( const Req& request, Res& response ) { - if ( !this->initialized_ ) { - return false; - } - if ( this->callback_ == nullptr ) { - return false; - } - return this->callback_( request, response ); -} - - -errCode SerialCommService::handle_packet( - const SerialCommProtoPacket& request, - SerialCommProtoPacket& response -) override { - if ( !this->initialized_ ) { - return errCode::ERR_NOT_INITIALIZED; - } - if ( this->callback_ == nullptr ) { - return errCode::ERR_NULL_POINTER; - } - - // Deserialize request - Req request_msg; - bool ret = Serializer::deserialize( - request.payload, - request.header.payload_len, - request_msg - ); - if ( !ret ) { - return errCode::ERR_PARSER; - } - - // Execute callback - Res response_msg; - ret = this->execute( request_msg, response_msg ); - if ( !ret ) { - return errCode::ERR_FAIL; - } - - // Serialize response - size_t serializer_size = 0; - ret = Serializer::serialize( - response_msg, - response.payload, - sizeof(response.payload), - serializer_size - ); - if ( !ret ) { - return errCode::ERR_PARSER; - } - - // Build response packet - SerialCommProtocol::clear_packet( response ); - response.header.seq_id = request.header.seq_id; - response.header.version = SERIAL_COMM_PROTOCOL_VER1; - response.header.command = make_reply( request.header.command ); - response.header.payload_len = static_cast( serializer_size ); - return errCode::OK; -} - - -errCode SerialCommService::init( Command command, service_callback_t callback ) { - if ( callback == nullptr ) { - return errCode::ERR_NULL_POINTER; - } - this->command_ = command; - this->callback_ = callback; - this->initialized_ = true; - return errCode::OK; -} - - -Command SerialCommService::command() const { - return this->command_; -} - - -bool SerialCommService::initialized() const { - return this->initialized_; -} - - -bool SerialCommService::valid() const { - return ( this->callback_ != nullptr ); -} diff --git a/middleware/topic/serial_comm_topic.cpp b/middleware/topic/serial_comm_topic.cpp deleted file mode 100644 index 3c7ed66..0000000 --- a/middleware/topic/serial_comm_topic.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @file serial_comm_topic.h - * @brief Generic Topic abstraction for SerialComm middleware - * @details Provides a ROS-like publish/subscribe abstraction over the - * SerialComm protocol. - * - * Responsibilities: - * - Typed publish/subscribe abstraction - * - Topic callback encapsulation - * - Lightweight async message delivery - * - * @author Bruno Gabriel Flores Sampaio - * @date Created on 26 of May, 2026 - */ - -#include "serial_comm_topic.h" - - -void SerialCommTopic::execute( const Msg& msg ) { - if ( !this->initialized_ ) { - return; - } - if ( this->callback_ == nullptr ) { - return; - } - this->callback_(msg); -} - - -errCode SerialCommTopic::handle_packet( const SerialCommProtoPacket& packet ) { - if ( !this->initialized_ ) { - return errCode::ERR_NOT_INITIALIZED; - } - Msg msg; - errCode res = Serializer::deserialize( - packet.payload, - packet.header.payload_len, - msg - ); - if ( res != errCode::OK ) { - return res; - } - this->execute( msg ); - return errCode::OK; -} - - -errCode SerialCommTopic::init( - Command command, - topic_callback_t callback -) { - if ( callback == nullptr ) { - return errCode::ERR_NULL_POINTER; - } - this->command_ = command; - this->callback_ = callback; - this->initialized_ = true; - return errCode::OK; -} - - -Command SerialCommTopic::command() const { - return this->command_; -} - - -bool SerialCommTopic::initialized() const { - return this->initialized_; -} - - -bool SerialCommTopic::valid() const { - return ( this->callback_ != nullptr ); -} - - \ No newline at end of file diff --git a/serial_comm_eventloop.cpp b/serial_comm_eventloop.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/core/README.md b/src/core/README.md similarity index 100% rename from core/README.md rename to src/core/README.md diff --git a/core/serial_comm.cpp b/src/core/serial_comm.cpp similarity index 89% rename from core/serial_comm.cpp rename to src/core/serial_comm.cpp index 4584413..709fed1 100644 --- a/core/serial_comm.cpp +++ b/src/core/serial_comm.cpp @@ -46,12 +46,23 @@ errCode SerialComm::init( const Config &cfg ) { // Dispatcher initialization SerialCommDispatcher::Config dispatcher_cfg = {}; dispatcher_cfg.task_name = "SerialCommDispatcher"; - dispatcher_cfg.rx_queue_len = 16; - dispatcher_cfg.task_stack_size = 4096; - dispatcher_cfg.task_priority = 5; + + dispatcher_cfg.rx_queue_len = + SerialCommConfig::DISPATCHER_QUEUE_SIZE; + + dispatcher_cfg.task_stack_size = + SerialCommConfig::DISPATCHER_TASK_STACK_SIZE; + + dispatcher_cfg.task_priority = + SerialCommConfig::DISPATCHER_TASK_PRIORITY; + errCode err = this->dispatcher_.init( dispatcher_cfg ); if ( err != errCode::OK ) { - ESP_LOGE( TAG, "Failed to initialize dispatcher: %s", err_to_str(err) ); + ESP_LOGE( + TAG, + "Failed to initialize dispatcher: %s", + err_to_str(err) + ); this->cleanup_resources(); return err; } @@ -101,7 +112,7 @@ errCode SerialComm::start() { return err; } // Start transport - errCode err = this->transport_->start(); + err = this->transport_->start(); if (err != errCode::OK) { ESP_LOGE( TAG, "Failed to start transport: %s", err_to_str(err) ); this->dispatcher_.stop(); @@ -200,9 +211,15 @@ errCode SerialComm::setup_interbyte_watchdog( ){ void SerialComm::on_interbyte_timeout() { if ( xSemaphoreTake( this->parser_mutex_, portMAX_DELAY ) == pdTRUE ) { - this->parser_.reset(); + bool had_partial_packet = + this->parser_.state() != SerialCommParser::State::WAIT_HEADER_0; + if ( had_partial_packet ) { + this->parser_.reset(); + } xSemaphoreGive( this->parser_mutex_ ); - ESP_LOGW( TAG, "Parser timeout reset" ); + if ( had_partial_packet ) { + ESP_LOGW( TAG, "Parser timeout reset" ); + } } } @@ -275,6 +292,12 @@ void SerialComm::process_rx_data( const uint8_t* data, size_t len ) { errCode err = this->dispatcher_.enqueue( packet ); if ( err != errCode::OK ) { ESP_LOGE( TAG, "Failed to enqueue packet" ); + } else { + ESP_LOGD( TAG, "Packet enqueued: Command=0x%02X, SeqID=%u, PayloadLen=%u", + packet.header.command, + packet.header.seq_id, + packet.header.payload_len + ); } } } diff --git a/core/serial_comm_dispatcher.cpp b/src/core/serial_comm_dispatcher.cpp similarity index 98% rename from core/serial_comm_dispatcher.cpp rename to src/core/serial_comm_dispatcher.cpp index 02b3336..005c8bc 100644 --- a/core/serial_comm_dispatcher.cpp +++ b/src/core/serial_comm_dispatcher.cpp @@ -137,7 +137,7 @@ errCode SerialCommDispatcher::enqueue( "RX queue full. Dropped command: 0x%02X", packet.header.command ); - return errCode::ERR_QUEUE_FULL; + return errCode::ERR_BUFFER_FULL; } // Update statistics @@ -205,7 +205,7 @@ void SerialCommDispatcher::dispatcher_task_entry( void* args ) { void SerialCommDispatcher::dispatcher_task() { - SerialCommProtoPacket packet; + static SerialCommProtoPacket packet; while ( true ) { if ( xQueueReceive( this->rx_queue_, &packet, portMAX_DELAY ) == pdTRUE ) { dispatch_packet( packet ); diff --git a/core/serial_comm_parser.cpp b/src/core/serial_comm_parser.cpp similarity index 94% rename from core/serial_comm_parser.cpp rename to src/core/serial_comm_parser.cpp index a4c30cb..f2a639f 100644 --- a/core/serial_comm_parser.cpp +++ b/src/core/serial_comm_parser.cpp @@ -10,7 +10,6 @@ /* To use the errCode and err_to_str easily */ using namespace SerialCommResult_Codes; - bool SerialCommParser::parse_byte( uint8_t byte, SerialCommProtoPacket& out_packet @@ -59,12 +58,10 @@ bool SerialCommParser::parse_byte( // READ VERSION case State::READ_VERSION: { packet_.header.version = byte; - if ( packet_.header.version != SERIAL_COMM_PROTOCOL_VER1 ) { this->reset(); break; } - this->state_ = State::READ_COMMAND; break; } @@ -89,11 +86,7 @@ bool SerialCommParser::parse_byte( packet_.header.payload_len |= (byte << 8); // VALIDATE PAYLOAD SIZE - size_t max_payload_size = - packet_.header.version == SERIAL_COMM_PROTOCOL_VER1 ? - SERIAL_COMM_MAX_PAYLOAD_V1 : - SERIAL_COMM_MAX_PAYLOAD_V2; - if ( packet_.header.payload_len > max_payload_size ) { + if ( packet_.header.payload_len > SERIAL_COMM_MAX_PAYLOAD ) { this->reset(); break; } @@ -171,7 +164,7 @@ bool SerialCommParser::parse_next_packet( consumed_bytes = 0; for (size_t i = 0; i < len; i++) { consumed_bytes++; - if ( parse_byte( data[i], out_packet ) ) { + if ( this->parse_byte( data[i], out_packet ) ) { return true; } } @@ -180,11 +173,11 @@ bool SerialCommParser::parse_next_packet( void SerialCommParser::reset() { - this->state_ = State::WAIT_HEADER_0; this->packet_.header.payload_len = 0; this->payload_index_ = 0; this->packet_.crc = 0; this->crc_l_ = 0; + this->state_ = State::WAIT_HEADER_0; } diff --git a/core/serial_comm_protocol.cpp b/src/core/serial_comm_protocol.cpp similarity index 98% rename from core/serial_comm_protocol.cpp rename to src/core/serial_comm_protocol.cpp index 61b3335..4a61109 100644 --- a/core/serial_comm_protocol.cpp +++ b/src/core/serial_comm_protocol.cpp @@ -125,7 +125,7 @@ int32_t SerialCommProtocol::decode( index += 2; // PAYLOAD SIZE VALIDATION - if ( out_packet.header.payload_len > SERIAL_COMM_MAX_PAYLOAD_V1 ) { + if ( out_packet.header.payload_len > SERIAL_COMM_MAX_PAYLOAD ) { return errCode::ERR_OVERFLOW; } size_t expected_size = @@ -215,7 +215,7 @@ bool SerialCommProtocol::validate_packet( return false; } // Validate payload length - if ( packet.header.payload_len > SERIAL_COMM_MAX_PAYLOAD_V1 ) { + if ( packet.header.payload_len > SERIAL_COMM_MAX_PAYLOAD ) { return false; } // Validate CRC diff --git a/core/serial_comm_watchdog.cpp b/src/core/serial_comm_watchdog.cpp similarity index 90% rename from core/serial_comm_watchdog.cpp rename to src/core/serial_comm_watchdog.cpp index 62940f5..ab74a5a 100644 --- a/core/serial_comm_watchdog.cpp +++ b/src/core/serial_comm_watchdog.cpp @@ -5,13 +5,10 @@ * @date Created on 26 of May, 2026 */ - -#pragma once - #include "serial_comm_watchdog.h" -static bool IRAM_ATTR SerialCommWatchdogTimer::timer_callback( +bool IRAM_ATTR SerialCommWatchdogTimer::timer_callback( gptimer_handle_t timer, const gptimer_alarm_event_data_t *data, void *user_ctx @@ -38,7 +35,7 @@ static bool IRAM_ATTR SerialCommWatchdogTimer::timer_callback( * waits for timer notification events. When it receives * a notification, it calls the user-defined callback. */ -static void SerialCommWatchdogTimer::task_function(void *pvArg){ +void SerialCommWatchdogTimer::task_function(void *pvArg){ SerialCommWatchdogTimer* timer = (SerialCommWatchdogTimer *)(pvArg); while (true) { ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); @@ -50,11 +47,11 @@ static void SerialCommWatchdogTimer::task_function(void *pvArg){ } SerialCommWatchdogTimer::SerialCommWatchdogTimer( - const char* task_name = "SerialCommWatchdog", - uint64_t timeout_us = uart_interbyte_timeout_us(SERIAL_COMM_UART_BAUDRATE), - Callback callback = nullptr, - uint32_t stack_size = 1024*4, - UBaseType_t priority = 5 + const char* task_name, + uint64_t timeout_us, + Callback callback, + uint32_t stack_size, + UBaseType_t priority ): _callback(callback), _timeout_us(timeout_us), diff --git a/middleware/README.md b/src/middleware/README.md similarity index 100% rename from middleware/README.md rename to src/middleware/README.md diff --git a/middleware/serial_comm_manager.cpp b/src/middleware/serial_comm_manager.cpp similarity index 97% rename from middleware/serial_comm_manager.cpp rename to src/middleware/serial_comm_manager.cpp index 979261c..cb5ebfe 100644 --- a/middleware/serial_comm_manager.cpp +++ b/src/middleware/serial_comm_manager.cpp @@ -39,10 +39,7 @@ errCode SerialCommManager::init( const Config& cfg ) { this->clear_registries(); // Init transaction manager - errCode res = this->transactions_.init( - cfg.enable_transactions, - cfg.service_timeout_ms - ); + errCode res = this->transactions_.init(); if ( res != errCode::OK ) { vSemaphoreDelete( this->seq_mutex_ ); this->seq_mutex_ = nullptr; @@ -169,8 +166,7 @@ errCode SerialCommManager::build_packet( size_t payload_len, SerialCommProtoPacket& out_packet ) { - if ( payload_len > SERIAL_COMM_MAX_PAYLOAD_V2 - ) { + if ( payload_len > SERIAL_COMM_MAX_PAYLOAD ) { return errCode::ERR_INVALID_ARG; } SerialCommProtocol::clear_packet( out_packet ); diff --git a/middleware/serial_comm_manager_action.cpp b/src/middleware/serial_comm_manager_action.cpp similarity index 55% rename from middleware/serial_comm_manager_action.cpp rename to src/middleware/serial_comm_manager_action.cpp index fd647da..63ddd98 100644 --- a/middleware/serial_comm_manager_action.cpp +++ b/src/middleware/serial_comm_manager_action.cpp @@ -7,32 +7,6 @@ #include "serial_comm_manager.h" -template< typename Goal, typename Feedback, typename Result > -errCode SerialCommManager::create_action( - SerialCommAction< Goal, Feedback, Result >* action -) { - if ( action == nullptr ) { - return errCode::ERR_NULL_POINTER; - } - if ( !action->initialized() ) { - return errCode::ERR_NOT_INITIALIZED; - } - xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); - for ( size_t i = 0; i < MAX_ACTIONS; i++ ) { - if ( !this->actions_[i].used ) { - this->actions_[i].used = true; - this->actions_[i].command = action->command(); - this->actions_[i].action = action; - xSemaphoreGive( this->registry_mutex_ ); - return errCode::OK; - } - } - xSemaphoreGive( this->registry_mutex_ ); - return errCode::ERR_NO_MEMORY; -} - - - bool SerialCommManager::is_action_command( Command command ) const { for ( size_t i = 0; i < MAX_ACTIONS; i++ ) { if ( diff --git a/src/middleware/serial_comm_manager_service.cpp b/src/middleware/serial_comm_manager_service.cpp new file mode 100644 index 0000000..5ad6f05 --- /dev/null +++ b/src/middleware/serial_comm_manager_service.cpp @@ -0,0 +1,89 @@ +/** + * @file serial_comm_manager_service.cpp + * @brief Serial communication middleware service handling + * implementation + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#include "serial_comm_manager.h" + +static const char* TAG = "SERIAL_COMM_MANAGER"; + + +void SerialCommManager::handle_service_request( + const SerialCommProtoPacket& packet +) { + ESP_LOGD( + TAG, + "Request packet received: cmd=0x%02X seq=%u len=%u", + static_cast( packet.header.command ), + packet.header.seq_id, + packet.header.payload_len + ); + auto* entry = this->find_service( packet.header.command ); + if ( entry == nullptr || entry->service == nullptr ) { + ESP_LOGW( + TAG, + "No service handler for command=0x%02X", + static_cast( packet.header.command ) + ); + return; + } + + static SerialCommProtoPacket response; + SerialCommProtocol::clear_packet( response ); + errCode res = entry->service->handle_packet( packet, response ); + if ( res != errCode::OK ) { + ESP_LOGW( + TAG, + "Service handler failed: cmd=0x%02X err=%s", + static_cast( packet.header.command ), + err_to_str( res ) + ); + return; + } + + if ( this->cfg_.enable_auto_reply ) { + res = this->serial_->send( response ); + if ( res != errCode::OK ) { + ESP_LOGW( + TAG, + "Failed to send service reply: cmd=0x%02X err=%s", + static_cast( packet.header.command ), + err_to_str( res ) + ); + } + } +} + + +SerialCommManager::ServiceEntry* SerialCommManager::find_service( + Command command +) { + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < MAX_SERVICES; i++ ) { + if ( + this->services_[i].used && + this->services_[i].command == command + ) { + xSemaphoreGive( this->registry_mutex_ ); + return &this->services_[i]; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return nullptr; +} + + +bool SerialCommManager::is_service_command( Command command ) const { + for ( size_t i = 0; i < MAX_SERVICES; i++ ) { + if ( + this->services_[i].used && + this->services_[i].command == command + ) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/src/middleware/serial_comm_manager_topic.cpp b/src/middleware/serial_comm_manager_topic.cpp new file mode 100644 index 0000000..e3e581e --- /dev/null +++ b/src/middleware/serial_comm_manager_topic.cpp @@ -0,0 +1,39 @@ +/** + * @file serial_comm_manager_topic.cpp + * @brief SerialCommManager topic handling implementation + * @author Bruno Gabriel Flores Sampaio + * @date Created on 26 of May, 2026 + */ + +#include "serial_comm_manager.h" + + +SerialCommManager::TopicEntry* SerialCommManager::find_topic( + Command command +) { + xSemaphoreTake( this->registry_mutex_, portMAX_DELAY ); + for ( size_t i = 0; i < MAX_TOPICS; i++ ) { + if ( + this->topics_[i].used && + this->topics_[i].command == command + ) { + xSemaphoreGive( this->registry_mutex_ ); + return &this->topics_[i]; + } + } + xSemaphoreGive( this->registry_mutex_ ); + return nullptr; +} + + +bool SerialCommManager::is_topic_command( Command command ) const { + for ( size_t i = 0; i < MAX_TOPICS; i++ ) { + if ( + this->topics_[i].used && + this->topics_[i].command == command + ) { + return true; + } + } + return false; +} diff --git a/middleware/serial_comm_transaction_manager.cpp b/src/middleware/serial_comm_transaction_manager.cpp similarity index 88% rename from middleware/serial_comm_transaction_manager.cpp rename to src/middleware/serial_comm_transaction_manager.cpp index d139fcd..599e43c 100644 --- a/middleware/serial_comm_transaction_manager.cpp +++ b/src/middleware/serial_comm_transaction_manager.cpp @@ -28,7 +28,7 @@ errCode SerialCommTransactionManager::init() { if ( this->mutex_ == nullptr ) { return errCode::ERR_NO_MEMORY; } - for ( size_t i = 0; i < SERIAL_COMM_MAX_TRANSACTIONS; i++ ) { + for ( size_t i = 0; i < MAX_TRANSACTIONS; i++ ) { this->transactions_[i].semaphore = xSemaphoreCreateBinary(); if ( this->transactions_[i].semaphore == nullptr ) { return errCode::ERR_NO_MEMORY; @@ -40,7 +40,7 @@ errCode SerialCommTransactionManager::init() { errCode SerialCommTransactionManager::deinit() { - for ( size_t i = 0; i < SERIAL_COMM_MAX_TRANSACTIONS; i++ ) { + for ( size_t i = 0; i < MAX_TRANSACTIONS; i++ ) { if ( this->transactions_[i].semaphore != nullptr ) { vSemaphoreDelete( this->transactions_[i].semaphore ); this->transactions_[i].semaphore = nullptr; @@ -60,7 +60,7 @@ errCode SerialCommTransactionManager::create_transaction( uint16_t seq_id ) { return errCode::ERR_NOT_INITIALIZED; } xSemaphoreTake( this->mutex_, portMAX_DELAY ); - for ( size_t i = 0; i < SERIAL_COMM_MAX_TRANSACTIONS; i++ ) { + for ( size_t i = 0; i < MAX_TRANSACTIONS; i++ ) { if ( !this->transactions_[i].active ) { this->transactions_[i].active = true; this->transactions_[i].completed = false; @@ -81,7 +81,7 @@ errCode SerialCommTransactionManager::wait_reply( ) { Transaction* transaction = this->find_transaction(seq_id); if ( transaction == nullptr ) { - return errCode::ERR_NOT_FOUND; + return errCode::ERR_EMPTY; } if ( xSemaphoreTake( transaction->semaphore, @@ -103,7 +103,7 @@ errCode SerialCommTransactionManager::resolve_transaction( Transaction* transaction = this->find_transaction( packet.header.seq_id ); if ( transaction == nullptr ) { - return errCode::ERR_NOT_FOUND; + return errCode::ERR_EMPTY; } transaction->reply = packet; transaction->completed = true; @@ -112,8 +112,8 @@ errCode SerialCommTransactionManager::resolve_transaction( } -Transaction* SerialCommTransactionManager::find_transaction( uint16_t seq_id ) { - for ( size_t i = 0; i < SERIAL_COMM_MAX_TRANSACTIONS; i++ ) { +SerialCommTransactionManager::Transaction* SerialCommTransactionManager::find_transaction( uint16_t seq_id ) { + for ( size_t i = 0; i < MAX_TRANSACTIONS; i++ ) { if ( this->transactions_[i].active && this->transactions_[i].seq_id == seq_id @@ -127,7 +127,7 @@ Transaction* SerialCommTransactionManager::find_transaction( uint16_t seq_id ) { void SerialCommTransactionManager::destroy_transaction( uint16_t seq_id ) { xSemaphoreTake( this->mutex_, portMAX_DELAY ); - for ( size_t i = 0; i < SERIAL_COMM_MAX_TRANSACTIONS; i++ ) { + for ( size_t i = 0; i < MAX_TRANSACTIONS; i++ ) { if ( this->transactions_[i].active && this->transactions_[i].seq_id == seq_id @@ -148,5 +148,5 @@ void SerialCommTransactionManager::destroy_transaction( uint16_t seq_id ) { SerialCommTransactionManager::~SerialCommTransactionManager() { - trhis->deinit(); + this->deinit(); }; diff --git a/transport/README.md b/src/transport/README.md similarity index 100% rename from transport/README.md rename to src/transport/README.md diff --git a/transport/uart_serial_comm.cpp b/src/transport/uart_serial_comm.cpp similarity index 85% rename from transport/uart_serial_comm.cpp rename to src/transport/uart_serial_comm.cpp index 1772cc6..65ce308 100644 --- a/transport/uart_serial_comm.cpp +++ b/src/transport/uart_serial_comm.cpp @@ -13,17 +13,20 @@ using namespace SerialCommResult_Codes; static const char* TAG = "UART_TRANSPORT"; -UARTTransport::UARTTransport( const HardwareConfig& hw_cfg ) - : hw_cfg_(hw_cfg), - state_(State::UNINITIALIZED), - uart_queue_(nullptr), - uart_task_(nullptr), - rx_callback_(nullptr), - tx_done_callback_(nullptr), - event_callback_(nullptr), - rx_ctx_(nullptr), - tx_ctx_(nullptr), - event_ctx_(nullptr) +UARTTransport::UARTTransport( const HardwareConfig& hw_cfg ) : + hw_cfg_(hw_cfg), + uart_mutex_(nullptr), + state_mutex_(nullptr), + uart_queue_(nullptr), + uart_task_(nullptr), + tx_done_callback_(nullptr), + event_callback_(nullptr), + rx_callback_(nullptr), + event_ctx_(nullptr), + rx_ctx_(nullptr), + tx_ctx_(nullptr), + cfg_{}, + state_(State::UNINITIALIZED) { } @@ -46,7 +49,7 @@ errCode UARTTransport::init( const Config& cfg ) { (uart_port_t)hw_cfg_.uart_port, cfg.rx_buffer_size, cfg.tx_buffer_size, - 32, + SerialCommConfig::UART_EVENT_QUEUE_SIZE, &uart_queue_, 0 ); @@ -100,15 +103,32 @@ errCode UARTTransport::start() { return errCode::ERR_INVALID_STATE; } - xTaskCreatePinnedToCore( - uart_event_task, - "uart_event_task", - 4096, - this, - 5, - &uart_task_, - 1 - ); + // Check if the Task have Core afinity enabled + BaseType_t task_result; + if ( SerialCommConfig::UART_USE_TASK_CORE_AFINITY ){ + task_result = xTaskCreatePinnedToCore( + uart_event_task, + "uart_event_task", + SerialCommConfig::UART_TASK_STACK_SIZE, + this, + SerialCommConfig::UART_TASK_PRIORITY, + &uart_task_, + SerialCommConfig::UART_TASK_CORE + ); + } else { + task_result = xTaskCreate( + uart_event_task, + "uart_event_task", + SerialCommConfig::UART_TASK_STACK_SIZE, + this, + SerialCommConfig::UART_TASK_PRIORITY, + &uart_task_ + ); + } + if ( task_result != pdPASS ) { + ESP_LOGE( TAG, "Failed to create UART event task" ); + return errCode::ERR_FAIL; + } state_ = State::RUNNING; ESP_LOGI( TAG, "UART started" );