785 lines
27 KiB
C++
785 lines
27 KiB
C++
//{{copyright}}
|
|
|
|
//{{version}}
|
|
|
|
//{{license}}
|
|
|
|
#pragma once
|
|
|
|
#include "../generic/crc.h"
|
|
|
|
#include <array>
|
|
#include <cstring>
|
|
#include <span>
|
|
#include <string>
|
|
|
|
/**
|
|
* @namespace sdi_ToolBox::comm::uBinaryFrame
|
|
* @brief Utilities for robust binary frame serialization, deserialization, and protocol handling.
|
|
*
|
|
* This namespace provides classes and constants to manage binary communication frames with automatic
|
|
* escaping, CRC calculation, and protocol validation. The supported frame format is:
|
|
* [SOF1][SOF2][SIZE][PAYLOAD][CRC16][EOF]
|
|
* where special bytes are escaped as needed.
|
|
*
|
|
* Main components:
|
|
* - @ref FrameReceiver : Receives and extracts a complete binary frame from a byte stream.
|
|
* - @ref MessageParser : Parses and validates a binary frame, providing typed access to the payload.
|
|
* - @ref MessageBuilder : Builds a binary message with CRC calculation and escaping.
|
|
* - @ref FramePacker : Packs a message into a complete frame ready for transmission (header, CRC, escaping, etc.).
|
|
*/
|
|
namespace sdi_ToolBox::comm::uBinaryFrame
|
|
{
|
|
constexpr uint8_t SOF1_ = 0xAA; // Start of Frame byte (0b10101010 - useful for bus synchronisation)
|
|
constexpr uint8_t SOF2_ = 0x55; // Start of Frame byte (0b01010101 - useful for bus synchronisation)
|
|
constexpr uint8_t EOF_ = 0x03; // End of Frame byte (End of Text code in ASCII table)
|
|
constexpr uint8_t ESC_ = 0x1B; // Escape byte (Escape code in ASCII table)
|
|
|
|
//--------------------------------------------------------------
|
|
/**
|
|
* @class FrameReceiver
|
|
* @brief Stateful receiver for extracting a complete binary frame from a byte stream.
|
|
*
|
|
* Handles frame synchronization, byte un-escaping, and buffering.
|
|
* The frame is considered complete when the protocol's end-of-frame byte is detected.
|
|
*
|
|
* @tparam BUFFER_SIZE Maximum size of the internal receive buffer.
|
|
*
|
|
* Usage:
|
|
* - Call push(byte) for each received byte.
|
|
* - When push() returns true or isFrameAvailable() is true, use getFrame() to access the frame data.
|
|
*
|
|
* @code
|
|
* FrameReceiver<128> receiver;
|
|
* for (uint8_t byte : incomingBytes) {
|
|
* if (receiver.push(byte)) {
|
|
* auto frame = receiver.getFrame();
|
|
* // Process frame...
|
|
*
|
|
* receiver.reset();
|
|
* }
|
|
* }
|
|
* @endcode
|
|
*/
|
|
template<size_t BUFFER_SIZE>
|
|
class FrameReceiver
|
|
{
|
|
enum class State : uint8_t
|
|
{
|
|
WAIT_SOF1,
|
|
WAIT_SOF2,
|
|
RECEIVE_DATA,
|
|
WAIT_EOF,
|
|
ESCAPE_NEXT,
|
|
END,
|
|
};
|
|
|
|
public:
|
|
FrameReceiver() = default; // Default constructor
|
|
~FrameReceiver() = default; // Default destructor
|
|
FrameReceiver(const FrameReceiver &obj) = default; // Copy constructor
|
|
FrameReceiver(FrameReceiver &&obj) noexcept = default; // Move constructor
|
|
FrameReceiver &operator=(const FrameReceiver &obj) = default; // Copy assignment operator
|
|
FrameReceiver &operator=(FrameReceiver &&obj) noexcept = default; // Move assignment operator
|
|
|
|
/**
|
|
* @brief Resets the receiver state and clears the internal buffer.
|
|
*
|
|
* After calling this method, the receiver is ready to process a new frame.
|
|
*/
|
|
void reset();
|
|
|
|
/**
|
|
* @brief Pushes a byte into the receiver buffer and updates the state machine.
|
|
* @param byte The received byte to process.
|
|
* @return true if a complete frame has been received and is available, false otherwise.
|
|
*
|
|
* @note If a frame is available, use getFrame() to access its content.
|
|
*/
|
|
[[nodiscard]] bool push(uint8_t byte);
|
|
|
|
/**
|
|
* @brief Checks if a complete frame is available in the buffer.
|
|
* @return true if a frame is available, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool isFrameAvailable() const;
|
|
|
|
/**
|
|
* @brief Returns a span to the complete frame data.
|
|
* @return A span containing the frame bytes, or an empty span if no frame is available.
|
|
*
|
|
* @note The returned span is only valid until the next reset() or push().
|
|
*/
|
|
[[nodiscard]] std::span<const uint8_t> getFrame() const;
|
|
|
|
protected:
|
|
std::array<uint8_t, BUFFER_SIZE> m_buffer{}; // Receiver buffer
|
|
size_t m_index = 0; // Current index in the buffer
|
|
State m_state = State::WAIT_SOF1; // Current state of the receiver
|
|
|
|
private:
|
|
void appendByte(uint8_t byte); // Append a byte to the buffer
|
|
};
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline void FrameReceiver<BUFFER_SIZE>::reset()
|
|
{
|
|
m_index = 0;
|
|
m_state = State::WAIT_SOF1;
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline bool FrameReceiver<BUFFER_SIZE>::push(const uint8_t byte)
|
|
{
|
|
switch (m_state)
|
|
{
|
|
// Initial state: waiting for SOF1
|
|
case State::WAIT_SOF1:
|
|
{
|
|
if (byte == SOF1_)
|
|
{
|
|
m_index = 0;
|
|
m_state = State::WAIT_SOF2; // Next byte should be SOF2
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Waiting for SOF2
|
|
case State::WAIT_SOF2:
|
|
{
|
|
if (byte == SOF2_)
|
|
m_state = State::RECEIVE_DATA; // Start receiving data
|
|
else
|
|
m_state = State::WAIT_SOF1; // Abort frame reception
|
|
break;
|
|
}
|
|
|
|
// Receiving data
|
|
case State::RECEIVE_DATA:
|
|
{
|
|
if (byte == ESC_)
|
|
m_state = State::ESCAPE_NEXT; // Next byte is escaped
|
|
else if (byte == SOF1_)
|
|
m_state = State::WAIT_SOF2; // Restart frame reception
|
|
else if (byte == SOF2_)
|
|
m_state = State::WAIT_SOF1; // Abort frame reception
|
|
else if (byte == EOF_)
|
|
{
|
|
m_state = State::END; // Frame complete
|
|
return true;
|
|
}
|
|
else
|
|
appendByte(byte);
|
|
break;
|
|
}
|
|
|
|
// Handling escaped byte
|
|
case State::ESCAPE_NEXT:
|
|
{
|
|
appendByte(byte);
|
|
m_state = State::RECEIVE_DATA; // Return to data reception
|
|
break;
|
|
}
|
|
|
|
// Frame complete
|
|
case State::END:
|
|
default:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline bool FrameReceiver<BUFFER_SIZE>::isFrameAvailable() const
|
|
{
|
|
return m_state == State::END;
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline std::span<const uint8_t> FrameReceiver<BUFFER_SIZE>::getFrame() const
|
|
{
|
|
if (m_state == State::END)
|
|
return { m_buffer.data(), m_index };
|
|
return {};
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline void FrameReceiver<BUFFER_SIZE>::appendByte(const uint8_t byte)
|
|
{
|
|
if (m_index < BUFFER_SIZE)
|
|
m_buffer[m_index++] = byte;
|
|
}
|
|
//--------------------------------------------------------------
|
|
|
|
/* --- */
|
|
|
|
//--------------------------------------------------------------
|
|
/**
|
|
* @class MessageParser
|
|
* @brief Parses and validates a binary frame, providing typed access to the payload.
|
|
*
|
|
* Checks frame structure, size, CRC, and end-of-frame marker.
|
|
* Allows extraction of typed values or raw data from the payload.
|
|
*
|
|
* Usage:
|
|
* - Construct with a frame buffer.
|
|
* - Check validity with isValid() or getStatus().
|
|
* - Use get<T>(offset, value) to extract typed data from the payload.
|
|
*
|
|
* @code
|
|
* MessageParser parser(frame);
|
|
* if (parser.isValid()) {
|
|
* uint16_t id;
|
|
* parser.get(0, id);
|
|
* std::array<uint8_t, 8> data;
|
|
* parser.get(2, std::span<uint8_t>(data));
|
|
* // Use id and data...
|
|
* }
|
|
* @endcode
|
|
*/
|
|
class MessageParser
|
|
{
|
|
public:
|
|
enum class MessageStatus : uint8_t
|
|
{
|
|
VALID,
|
|
INVALID,
|
|
BAD_SIZE,
|
|
BAD_CRC,
|
|
};
|
|
|
|
public:
|
|
MessageParser() = delete; // Default constructor
|
|
~MessageParser() = default; // Default destructor
|
|
MessageParser(const MessageParser &obj) = default; // Copy constructor
|
|
MessageParser(MessageParser &&obj) noexcept = default; // Move constructor
|
|
MessageParser &operator=(const MessageParser &obj) = default; // Copy assignment operator
|
|
MessageParser &operator=(MessageParser &&obj) noexcept = default; // Move assignment operator
|
|
|
|
/**
|
|
* @brief Constructs a MessageParser and checks the integrity of the provided frame.
|
|
* @param buffer The buffer containing the frame to parse.
|
|
*/
|
|
explicit MessageParser(std::span<const uint8_t> buffer);
|
|
|
|
/**
|
|
* @brief Checks if the parsed message is valid.
|
|
* @return true if the message is valid, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool isValid() const;
|
|
|
|
/**
|
|
* @brief Gets the current message validity status.
|
|
* @return The status as a MessageStatus enum value.
|
|
*/
|
|
[[nodiscard]] MessageStatus getStatus() const;
|
|
|
|
/**
|
|
* @brief Gets the message payload buffer.
|
|
* @return A span containing the payload bytes.
|
|
*/
|
|
[[nodiscard]] std::span<const uint8_t> getBuffer() const;
|
|
|
|
/**
|
|
* @brief Gets the size of the message payload.
|
|
* @return The size of the payload in bytes.
|
|
*/
|
|
[[nodiscard]] size_t size() const; // Get message payload size
|
|
|
|
/**
|
|
* @brief Extracts a value of type T from the message payload at the specified offset.
|
|
* @tparam T The type of the value to extract (POD type or std::span<uint8_t>).
|
|
* @param offset The offset in the payload to read from.
|
|
* @param value Reference to store the extracted value.
|
|
* @return The new offset after reading the value, or the original offset if reading failed.
|
|
*
|
|
* @note No exception is thrown. If extraction fails, value is not modified.
|
|
*/
|
|
template<class T>
|
|
[[nodiscard]] size_t get(size_t offset, T &value) const;
|
|
|
|
protected:
|
|
std::span<const uint8_t> m_buffer; // Message buffer
|
|
MessageStatus m_messageStatus = MessageStatus::INVALID; // Message validity status
|
|
|
|
private:
|
|
void messageCheck(); // Check message integrity
|
|
};
|
|
//--------------------------------------------------------------
|
|
/* Constructor */
|
|
inline MessageParser::MessageParser(const std::span<const uint8_t> buffer)
|
|
{
|
|
m_buffer = buffer;
|
|
messageCheck();
|
|
}
|
|
//--------------------------------------------------------------
|
|
inline bool MessageParser::isValid() const
|
|
{
|
|
return m_messageStatus == MessageStatus::VALID;
|
|
}
|
|
//--------------------------------------------------------------
|
|
/* Get message validity status */
|
|
inline MessageParser::MessageStatus MessageParser::getStatus() const
|
|
{
|
|
return m_messageStatus;
|
|
}
|
|
//--------------------------------------------------------------
|
|
/* Get message payload buffer */
|
|
inline std::span<const uint8_t> MessageParser::getBuffer() const
|
|
{
|
|
return m_buffer;
|
|
}
|
|
//--------------------------------------------------------------
|
|
/* Get message payload size */
|
|
inline size_t MessageParser::size() const
|
|
{
|
|
return m_buffer.size();
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<class T>
|
|
inline size_t MessageParser::get(size_t offset, T &value) const
|
|
{
|
|
static_assert((std::is_standard_layout_v<T> && std::is_trivial_v<T>) || // POD type
|
|
std::is_same_v<T, std::span<uint8_t>> || // std::span<uint8_t> type
|
|
std::is_same_v<T, std::string>); // std::string type
|
|
|
|
if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
|
|
{
|
|
// POD type
|
|
constexpr size_t typeSize = sizeof(T);
|
|
if (offset + typeSize <= m_buffer.size())
|
|
{
|
|
std::memcpy(&value, m_buffer.data() + offset, typeSize);
|
|
return offset + typeSize; // Return new offset
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, std::span<uint8_t>>)
|
|
{
|
|
// std::span<uint8_t> type
|
|
if (offset + value.size() <= m_buffer.size())
|
|
{
|
|
std::memcpy(value.data(), m_buffer.data() + offset, value.size());
|
|
return offset + value.size(); // Return new offset
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, std::string>)
|
|
{
|
|
// std::string type
|
|
uint8_t strSize;
|
|
offset = get(offset, strSize);
|
|
|
|
value.resize(strSize);
|
|
std::span<uint8_t> strSpan(reinterpret_cast<uint8_t *>(value.data()), strSize);
|
|
offset = get(offset, strSpan);
|
|
}
|
|
|
|
return offset; // Unable to read value, return original offset
|
|
}
|
|
//--------------------------------------------------------------
|
|
/* Check message integrity */
|
|
inline void MessageParser::messageCheck()
|
|
{
|
|
/* Message format : [SOF1(1)][SOF2(1)][SIZE(1)][PAYLOAD(n)][CRC16(2)][EOF(1)] */
|
|
// Check SIZE
|
|
const auto size = m_buffer[0];
|
|
if (constexpr size_t CRCSize = 2; m_buffer.size() != CRCSize + size + sizeof(size))
|
|
{
|
|
m_messageStatus = MessageStatus::BAD_SIZE;
|
|
return;
|
|
}
|
|
|
|
// Check CRC16
|
|
const auto loCRC = *(m_buffer.rbegin() + 1);
|
|
const auto hiCRC = *m_buffer.rbegin();
|
|
const uint16_t frameCRC = static_cast<uint16_t>(hiCRC << 8) | static_cast<uint16_t>(loCRC);
|
|
const uint16_t computedCRC = generic::CRC16_modbus::computeCRC(m_buffer.subspan(1, m_buffer.size() - 3));
|
|
if (frameCRC != computedCRC)
|
|
{
|
|
m_messageStatus = MessageStatus::BAD_CRC;
|
|
return;
|
|
}
|
|
|
|
m_buffer = m_buffer.subspan(1, size);
|
|
m_messageStatus = MessageStatus::VALID;
|
|
}
|
|
//--------------------------------------------------------------
|
|
|
|
/* --- */
|
|
|
|
//--------------------------------------------------------------
|
|
/**
|
|
* @class MessageBuilderHelper
|
|
* @brief Utility class to simplify the usage of MessageBuilder templates.
|
|
*
|
|
* This helper provides convenient methods and type aliases to work with MessageBuilder
|
|
* instances without exposing template parameters in function signatures.
|
|
*/
|
|
class MessageBuilderHelper
|
|
{
|
|
public:
|
|
MessageBuilderHelper() = default; // Default constructor
|
|
virtual ~MessageBuilderHelper() = default; // Default destructor
|
|
MessageBuilderHelper(const MessageBuilderHelper &obj) = default; // Copy constructor
|
|
MessageBuilderHelper(MessageBuilderHelper &&obj) noexcept = default; // Move constructor
|
|
MessageBuilderHelper &operator=(const MessageBuilderHelper &obj) = default; // Copy assignment operator
|
|
MessageBuilderHelper &operator=(MessageBuilderHelper &&obj) noexcept = default; // Move assignment operator
|
|
|
|
/**
|
|
* @brief Gets the current message buffer as a span.
|
|
* @return A span containing the current message bytes.
|
|
*/
|
|
[[nodiscard]] virtual std::span<const uint8_t> getBuffer() const = 0;
|
|
|
|
/**
|
|
* @brief Checks if the message buffer is full.
|
|
* @return true if the buffer is full, false otherwise.
|
|
*/
|
|
[[nodiscard]] virtual bool isFull() const = 0;
|
|
|
|
/**
|
|
* @brief Gets the current size of the message buffer.
|
|
* @return The number of bytes currently in the message buffer.
|
|
*/
|
|
[[nodiscard]] virtual size_t getBufferSize() const = 0;
|
|
|
|
/**
|
|
* @brief Gets the current CRC16 value of the message.
|
|
* @return The current CRC16 value.
|
|
*/
|
|
[[nodiscard]] virtual uint16_t getCRC() const = 0;
|
|
};
|
|
//--------------------------------------------------------------
|
|
|
|
/* --- */
|
|
|
|
//--------------------------------------------------------------
|
|
/**
|
|
* @class MessageBuilder
|
|
* @brief Builds a binary message payload with CRC calculation and escaping support.
|
|
*
|
|
* Supports appending POD types or byte spans, and computes CRC incrementally.
|
|
* Provides methods to reset, append, and retrieve the current buffer and CRC.
|
|
*
|
|
* @tparam BUFFER_SIZE Maximum size of the internal message buffer.
|
|
*
|
|
* Usage:
|
|
* - Use append() or operator<< to add data.
|
|
* - Retrieve the buffer with getBuffer() and CRC with getCRC().
|
|
*
|
|
* @code
|
|
* MessageBuilder<64> builder;
|
|
* builder << uint16_t(0x1234) << std::span<const uint8_t>(data, dataLen);
|
|
* auto buffer = builder.getBuffer();
|
|
* auto crc = builder.getCRC();
|
|
* @endcode
|
|
*/
|
|
template<size_t BUFFER_SIZE>
|
|
class MessageBuilder : public MessageBuilderHelper
|
|
{
|
|
public:
|
|
MessageBuilder(); // Default constructor
|
|
virtual ~MessageBuilder() = default; // Default destructor
|
|
MessageBuilder(const MessageBuilder &obj) = default; // Copy constructor
|
|
MessageBuilder(MessageBuilder &&obj) noexcept = default; // Move constructor
|
|
MessageBuilder &operator=(const MessageBuilder &obj) = default; // Copy assignment operator
|
|
MessageBuilder &operator=(MessageBuilder &&obj) noexcept = default; // Move assignment operator
|
|
|
|
/**
|
|
* @brief Resets the message builder to an empty state.
|
|
* @return Reference to the MessageBuilder for chaining.
|
|
*/
|
|
MessageBuilder &reset(); // Reset the message builder
|
|
|
|
/**
|
|
* @brief Appends a value of type T to the message buffer.
|
|
* @tparam T The type of the value to append (POD type or std::span<uint8_t>).
|
|
* @param value The value to append.
|
|
* @return Reference to the MessageBuilder for chaining.
|
|
*
|
|
* @note If appending the value would exceed the buffer size, it is ignored.
|
|
*/
|
|
template<class T>
|
|
MessageBuilder &append(const T &value); // Append value of type T to the message
|
|
|
|
/**
|
|
* @brief Overloads the << operator to append a value of type T to the message buffer.
|
|
* @tparam T The type of the value to append (POD type or std::span<uint8_t>).
|
|
* @param value The value to append.
|
|
* @return Reference to the MessageBuilder for chaining.
|
|
*
|
|
* @note If appending the value would exceed the buffer size, it is ignored.
|
|
*/
|
|
template<class T>
|
|
MessageBuilder &operator<<(const T &value);
|
|
|
|
[[nodiscard]] std::span<const uint8_t> getBuffer() const override;
|
|
[[nodiscard]] bool isFull() const override;
|
|
[[nodiscard]] size_t getBufferSize() const override;
|
|
[[nodiscard]] uint16_t getCRC() const override;
|
|
|
|
protected:
|
|
std::array<uint8_t, BUFFER_SIZE> m_buffer{}; // Receiver buffer
|
|
size_t m_index = 0; // Current index in the buffer
|
|
|
|
generic::CRC16_modbus m_CRC; // Current CRC value
|
|
};
|
|
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline MessageBuilder<BUFFER_SIZE>::MessageBuilder()
|
|
{
|
|
m_CRC.init();
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::reset()
|
|
{
|
|
m_index = 0;
|
|
m_CRC.init();
|
|
return *this; // Return reference to self for chaining
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
template<class T>
|
|
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::append(const T &value)
|
|
{
|
|
static_assert((std::is_standard_layout_v<T> && std::is_trivial_v<T>) || // POD type
|
|
std::is_same_v<T, std::span<uint8_t>> || // std::span<uint8_t> type
|
|
std::is_same_v<T, std::span<const uint8_t>> || // std::span<const uint8_t> type
|
|
std::is_same_v<T, std::string> || // std::string type
|
|
std::is_same_v<T, const std::string>); // const std::string type
|
|
|
|
if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
|
|
{
|
|
// POD type
|
|
constexpr size_t typeSize = sizeof(T);
|
|
if (m_index + typeSize <= BUFFER_SIZE)
|
|
{
|
|
std::array<uint8_t, typeSize> dataBuffer;
|
|
std::memcpy(dataBuffer.data(), &value, typeSize);
|
|
|
|
std::memcpy(m_buffer.data() + m_index, dataBuffer.data(), dataBuffer.size());
|
|
m_CRC.update(dataBuffer);
|
|
m_index += typeSize;
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, std::span<uint8_t>> ||
|
|
std::is_same_v<T, std::span<const uint8_t>>)
|
|
{
|
|
// std::span<uint8_t> type
|
|
if (m_index + value.size() <= BUFFER_SIZE)
|
|
{
|
|
std::memcpy(m_buffer.data() + m_index, value.data(), value.size());
|
|
m_CRC.update(value);
|
|
m_index += value.size();
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, const std::string>)
|
|
{
|
|
// std::string type
|
|
if (m_index + (value.size() + sizeof(uint8_t)) <= BUFFER_SIZE)
|
|
{
|
|
append(static_cast<uint8_t>(value.size()));
|
|
|
|
std::memcpy(m_buffer.data() + m_index, value.data(), value.size());
|
|
m_CRC.update(std::span(reinterpret_cast<const uint8_t *>(value.data()), value.size()));
|
|
m_index += value.size();
|
|
}
|
|
}
|
|
|
|
return *this; // Return reference to self for chaining
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
template<class T>
|
|
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::operator<<(const T &value)
|
|
{
|
|
append(value);
|
|
return *this; // Return reference to self for chaining
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline std::span<const uint8_t> MessageBuilder<BUFFER_SIZE>::getBuffer() const
|
|
{
|
|
return { m_buffer.data(), m_index };
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline bool MessageBuilder<BUFFER_SIZE>::isFull() const
|
|
{
|
|
return m_index >= BUFFER_SIZE;
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline size_t MessageBuilder<BUFFER_SIZE>::getBufferSize() const
|
|
{
|
|
return m_index;
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline uint16_t MessageBuilder<BUFFER_SIZE>::getCRC() const
|
|
{
|
|
return m_CRC.getCRC();
|
|
}
|
|
//--------------------------------------------------------------
|
|
|
|
/* --- */
|
|
|
|
//--------------------------------------------------------------
|
|
/**
|
|
* @class FramePacker
|
|
* @brief Packs a message payload into a complete binary frame ready for transmission.
|
|
*
|
|
* Adds protocol headers, escapes special bytes, appends CRC and end-of-frame marker.
|
|
* Ensures the packed frame fits in the provided buffer size.
|
|
*
|
|
* @tparam BUFFER_SIZE Maximum size of the output frame buffer.
|
|
*
|
|
* Usage:
|
|
* - Call packMessage(payload, crc) to build the frame.
|
|
* - Use isValid() to check for overflow.
|
|
* - Retrieve the packed frame with getPackedFrame().
|
|
*
|
|
* @code
|
|
* FramePacker<256> packer;
|
|
* if (packer.packMessage(payload, crc)) {
|
|
* auto frame = packer.getPackedFrame();
|
|
* // Send frame...
|
|
* }
|
|
* @endcode
|
|
*/
|
|
template<size_t BUFFER_SIZE>
|
|
class FramePacker
|
|
{
|
|
static constexpr size_t CRC_SIZE = 4; // CRC16(2 x2) --> (CRC is 4 bytes to support escaped CRC)
|
|
|
|
public:
|
|
FramePacker() = default; // Default constructor
|
|
explicit FramePacker(std::span<const uint8_t> message, uint16_t crc); // Constructor
|
|
~FramePacker() = default; // Default destructor
|
|
FramePacker(const FramePacker &obj) = default; // Copy constructor
|
|
FramePacker(FramePacker &&obj) noexcept = default; // Move constructor
|
|
FramePacker &operator=(const FramePacker &obj) = default; // Copy assignment operator
|
|
FramePacker &operator=(FramePacker &&obj) noexcept = default; // Move assignment operator
|
|
|
|
/**
|
|
* @brief Packs the input message into a complete frame with headers, CRC, and escaping.
|
|
* @param message The message payload to pack.
|
|
* @param crc The CRC16 value to append to the frame.
|
|
* @return true if packing was successful without overflow, false otherwise.
|
|
*/
|
|
bool packMessage(std::span<const uint8_t> message, uint16_t crc);
|
|
|
|
/**
|
|
* @brief Checks if the packed frame is valid (no overflow occurred).
|
|
* @return true if the packed frame is valid, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool isValid() const;
|
|
|
|
/**
|
|
* @brief Gets the packed frame buffer as a span.
|
|
* @return A span containing the packed frame bytes.
|
|
*/
|
|
[[nodiscard]] std::span<const uint8_t> getPackedFrame() const;
|
|
|
|
protected:
|
|
std::array<uint8_t, BUFFER_SIZE> m_frame{}; // Output buffer
|
|
size_t m_index = 0; // Current index in output buffer
|
|
|
|
std::array<uint8_t, CRC_SIZE> m_CRCBuffer{}; // CRC buffer with escaping
|
|
size_t m_CRCSize = 0; // Current size of CRC buffer with escaping
|
|
|
|
bool m_overflow = false; // Flag indicating if overflow occurred during packing
|
|
|
|
private:
|
|
void appendByte(uint8_t byte); // Append byte with escaping to output buffer
|
|
void appendRawByte(uint8_t byte); // Append raw byte to output buffer
|
|
|
|
void packCRC(uint16_t crc); // Pack CRC into CRC buffer with escaping
|
|
};
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline FramePacker<BUFFER_SIZE>::FramePacker(const std::span<const uint8_t> message, const uint16_t crc)
|
|
{
|
|
packMessage(message, crc);
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline bool FramePacker<BUFFER_SIZE>::packMessage(const std::span<const uint8_t> message, const uint16_t crc)
|
|
{
|
|
// Reset state
|
|
m_index = 0;
|
|
m_overflow = false;
|
|
|
|
// Prepare CRC buffer with escaping
|
|
packCRC(crc);
|
|
|
|
// Append header
|
|
appendRawByte(SOF1_); // Append SOF1
|
|
appendRawByte(SOF2_); // Append SOF2
|
|
appendByte(static_cast<uint8_t>(message.size())); // Append payload size
|
|
|
|
// Append payload
|
|
for (const auto &byte : message)
|
|
appendByte(byte);
|
|
|
|
// Append CRC
|
|
for (size_t i = 0; i < m_CRCSize; i++)
|
|
appendRawByte(m_CRCBuffer[i]);
|
|
|
|
// Append EOF
|
|
appendRawByte(EOF_);
|
|
|
|
return !m_overflow; // Return true if packing was successful
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline bool FramePacker<BUFFER_SIZE>::isValid() const
|
|
{
|
|
return !m_overflow;
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline std::span<const uint8_t> FramePacker<BUFFER_SIZE>::getPackedFrame() const
|
|
{
|
|
return std::span<const uint8_t>(m_frame.data(), m_index);
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline void FramePacker<BUFFER_SIZE>::appendByte(const uint8_t byte)
|
|
{
|
|
if (byte == SOF1_ || byte == SOF2_ || byte == EOF_ || byte == ESC_)
|
|
{
|
|
appendRawByte(ESC_); // Append escape byte
|
|
}
|
|
appendRawByte(byte); // Append actual byte
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline void FramePacker<BUFFER_SIZE>::appendRawByte(const uint8_t byte)
|
|
{
|
|
if (m_index < BUFFER_SIZE)
|
|
m_frame[m_index++] = byte;
|
|
else
|
|
m_overflow = true;
|
|
}
|
|
//--------------------------------------------------------------
|
|
template<size_t BUFFER_SIZE>
|
|
inline void FramePacker<BUFFER_SIZE>::packCRC(const uint16_t crc)
|
|
{
|
|
m_CRCSize = 0;
|
|
const uint8_t loCRC = static_cast<uint8_t>(crc & 0x00FF);
|
|
const uint8_t hiCRC = static_cast<uint8_t>((crc >> 8) & 0x00FF);
|
|
|
|
if (loCRC == SOF1_ || loCRC == SOF2_ || loCRC == EOF_ || loCRC == ESC_)
|
|
m_CRCBuffer[m_CRCSize++] = ESC_;
|
|
m_CRCBuffer[m_CRCSize++] = loCRC;
|
|
|
|
if (hiCRC == SOF1_ || hiCRC == SOF2_ || hiCRC == EOF_ || hiCRC == ESC_)
|
|
m_CRCBuffer[m_CRCSize++] = ESC_;
|
|
m_CRCBuffer[m_CRCSize++] = hiCRC;
|
|
}
|
|
//--------------------------------------------------------------
|
|
} // namespace sdi_ToolBox::comm::uBinaryFrame
|