Communication Protocol based on Streams. Uses Multiplexing to differentiate data channels. It is originally being developed for the communication between the RadonUlzer and the DroidControlShip projects.
- Using PlatformIO CLI:
pio pkg install --library "gabryelreyes/SerialMuxProt@^2.0.0"
- Adding library to
lib_deps
manually:
lib_deps =
gabryelreyes/SerialMuxProt@^2.0.0
- Navigate to the root of the Python library:
cd python/SerialMuxProt
- Install the package using
pip
:
pip install .
- Server-Client Architecture
- One-to-one. One Server to one client.
The Protocol sends and received Frames of the following form:
/** Data container of the Frame Fields */
typedef union _Frame
{
/** Frame Fields */
struct _Fields
{
/** Header */
union _Header
{
/** Header Fields Struct */
struct _HeaderFields
{
/** Channel ID */
uint8_t m_channel;
/** Channel DLC */
uint8_t m_dlc;
/** Frame Checksum */
uint8_t m_checksum;
} __attribute__((packed)) headerFields; /**< Header Fields */
/** Raw Header Data*/
uint8_t rawHeader[HEADER_LEN];
} __attribute__((packed)) header; /**< Header */
/** Payload */
struct _Payload
{
/** Data of the Frame */
uint8_t m_data[MAX_DATA_LEN];
} __attribute__((packed)) payload; /**< Payload */
} __attribute__((packed)) fields; /**< Frame Fields */
/** Raw Frame Data */
uint8_t raw[MAX_FRAME_LEN] = {0U};
} __attribute__((packed)) Frame; /**< Frame */
- Length: 1 Byte.
- Channel on which the data is being sent.
- Channel 0 is reserved for the server.
- Channels 1 to 255 are "Data Channels".
- The Application can publish or subscribe to any of these channels using the channel's name.
- Client suscribes to a channel using Channel 0.
- Contains the size of the payload contained by the frame.
- checksum = sum(Channel + DLC + Data Bytes) % UINT8_MAX
- Data Length: Set by the DLC Field.
- Contains Application Data Bytes.
- Control Channel: Exchange of Commands. Can not be used to send data.
- Channel 0 has no external Callback. The state of the server can be polled by the application through getter functions ( or similar depending on the implementation).
- D0 (Data Byte 0) is used as a Command Byte. Defines the command that is being sent.
- Even-number Commands in D0 are used as Commands, while uneven-number Commands are used as the response to the immediately previous (n-1) command.
- D0 = 0x00
- Server sends SYNC Command with current timestamp.
- Client responds with SYNC_RSP.
- Server can calculate Round-Trip-Time.
- SYNC Package must be sent periodically depending on current State. The period is also used as a timeout for the previous SYNC.
- Used as a "Heartbeat" or "keep-alive" by the client.
- D0 = 0x01
- Client Response to SYNC.
- Data Payload is the same timestamp as in SYNC Command.
- D0 = 0x02
- Client sends the name of the channel it wants to suscribe to.
- Server responds with the number and the name of the requested channel, if it is found and valid. If the channel is not found, the response has channel number = 0.
- D0 = 0x03
- Server Response to SCRB.
- Channel Number on Data Byte 1 (D1).
- Channel Name on the following bytes
- Information is sent directly from application to the Serial Driver. No queueing or buffering.
- The Protocol can send a maximum of 255 Bytes.
/**
* Channel Definition.
*/
struct Channel
{
char m_name[CHANNEL_NAME_MAX_LEN]; /**< Name of the channel. */
uint8_t m_dlc; /**< Payload length of channel */
ChannelCallback m_callback; /**< Callback to provide received data to the application. */
/**
* Channel Constructor.
*/
Channel() : m_name{0U}, m_dlc(0U), m_callback(nullptr)
{
}
};
- Channel has 3 members: Name, DLC, and callback function.
- Application initializes a channel with a name and a DLC, protocol looks for a free channel number and returns its channel number to the application.
- If no channel is free, it returns 0 as it is an invalid Data Channel.
- Application can subscribe to a remote data channel by its name and a callback to the function that must be called when data is received in said channel.
- Function has no return value, as the response from the server is asynchron.
/**
* Channel Notification Prototype Callback.
* Provides the received data in the respective channel to the application.
*
* @param[in] payload Received data.
* @param[in] payloadSize Size of the received data.
* @param[in] userData User data provided by the application.
*/
typedef void (*ChannelCallback)(const uint8_t* payload, const uint8_t payloadSize, void* userData);
- Callback passes only a pointer to the received Buffer. Data must be copied by application.
- Memory is freed by the protocol after the callback is done.
- DLC is passed as payloadSize to the application.
- The
userData
pointer specified in the constructor is passed to the application.
- Client is disconnected/does not respond to SYNC Command.
- No external data is sent in this state.
- SYNC Period set to 1 second.
- Client is connected and responds to SYNC Commands.
- SYNC Period set to 5 seconds.
It is possible to register EventCallback
callbacks for the Synced and DeSynced events. These will be called once an event is triggered to notify the user.
/**
* Event Notification Prototype Callback.
* Provides a notification to the application on the event it is registered to.
*
* @param[in] userData User data provided by the application.
*/
typedef void (*EventCallback)(void* userData);
/* Called on Sync. */
bool registerOnSyncedCallback(EventCallback callback);
/* Called on DeSync. */
bool registerOnDeSyncedCallback(EventCallback callback);
The SerialMuxChannels.h
file should be used to define the structures and channel information to be shared between two instances of the SerialMuxServer.
This file defines the Channel Names, DLCs, and the data structures of the payloads.
It is important to note that the structs must include the packed
attribute in order to ensure the access to the data correctly.
A sample file can be found in here.