Repository navigation
CommsChannels
Comms Channels are Raft's transport-agnostic message pipes. Each channel binds an interface (BLE, serial, sockets, WebSocket, …) to a protocol codec (RICSerial, RICFrame, RICJSON, …) and provides framed, queued, flow-controlled message delivery between the device and a peer.
For the larger picture see Communications Stack Overview.
| Class | Role |
|---|---|
CommsCoreIF |
Public interface used by SysMods to register channels, register protocols, and send/receive messages. |
CommsChannelManager |
The concrete implementation of CommsCoreIF. Itself a SysMod — created by SysManager early in startup so other SysMods can register channels in their addCommsChannels() override. |
CommsChannel |
One bidirectional channel: name, interface, protocol codec, inbound queue, outbound queue, settings. |
ProtocolBase |
Base class for protocol codecs. Implementations: ProtocolRICSerial, ProtocolRICFrame, ProtocolRICJSON, ProtocolROSSerial. |
CommsChannelMsg |
Decoded message: channelID, protocol code, msg number, type code, payload bytes. |
CommsChannelSettings |
Per-channel block sizes and queue depths. |
CommsChannelBridge |
Forwards messages between two channels. |
-
CommsChannelManageris constructed as a SysMod (typically byRaftCoreApp). -
Protocol codecs are added by SysMods in their
addCommsChannels(CommsCoreIF&)override. The standard set (RICSerial,RICFrame,RICJSON) is added by ProtocolExchange. -
Transport SysMods register channels also from
addCommsChannels(). Each call toregisterChannel()returns achannelID. - At runtime the manager allocates the actual codec object lazily the first time a channel needs to encode or decode.
-
Inbound: the transport calls
inboundHandleMsg(channelID, bytes, len)whenever it has raw bytes; the manager queues them and the channel's codec deframes them on its next service. -
Outbound: anything in the system calls
outboundHandleMsg(CommsChannelMsg&); the manager finds the channel by ID, encodes via its codec and calls the channel'soutboundHandleMsgCB. See Message queues for when that happens.
The full interface lives in CommsCoreIF.h. The methods most useful from a SysMod are:
uint32_t registerChannel(const char* protocolName,
const char* interfaceName,
const char* channelName,
CommsChannelOutboundHandleMsgFnType outboundHandleMsgCB,
CommsChannelOutboundCanAcceptFnType outboundCanAcceptCB,
const CommsChannelSettings* pSettings = nullptr);
void addProtocol(ProtocolCodecFactoryHelper& protocolDef);
bool inboundCanAccept(uint32_t channelID);
void inboundHandleMsg(uint32_t channelID, const uint8_t* pMsg, uint32_t msgLen);
void inboundHandleMsgVec(uint32_t channelID, const SpiramAwareUint8Vector& msg);
bool outboundCanAccept(uint32_t channelID, CommsMsgTypeCode msgType, bool& noConn);
CommsCoreRetCode outboundHandleMsg(CommsChannelMsg& msg);
int32_t getChannelIDByName(const String& channelName, const String& protocolName);There are also bridge methods (bridgeRegister, bridgeUnregister, bridgeHandleInboundMsg, bridgeHandleOutboundMsg) — see Bridges below.
| Constant | Value | Meaning |
|---|---|---|
CommsCoreIF::CHANNEL_ID_UNDEFINED |
0xffff |
Unset/invalid |
CommsCoreIF::CHANNEL_ID_REST_API |
0xfffe |
Used by HTTP requests (they don't have a real channel) |
MSG_CHANNEL_ID_ALL |
10000 |
Broadcast — all channels |
This is a minimal transport SysMod that exposes one channel:
class MyTransport : public RaftSysMod
{
public:
MyTransport(const char* pModuleName, RaftJsonIF& sysConfig)
: RaftSysMod(pModuleName, sysConfig) {}
protected:
virtual void addCommsChannels(CommsCoreIF& commsCore) override final
{
// Choose a protocol that matches the wire format of this transport.
// RICSerial = HDLC-framed binary; RICFrame = length-prefixed; RICJSON = pure JSON.
static const CommsChannelSettings settings(
/*inboundBlockLen */ 1200,
/*inboundBlockMax */ 5000,
/*inboundQueueLen */ 0, // 0 = default
/*inboundQueueBytes */ 0,
/*outboundBlockLen */ 5000,
/*outboundQueueLen */ 0);
_channelID = commsCore.registerChannel(
/*protocolName */ "RICSerial",
/*interfaceName */ "MyIF",
/*channelName */ "MyIF",
/*outboundCB */ std::bind(&MyTransport::sendBytes, this, std::placeholders::_1),
/*canAcceptCB */ std::bind(&MyTransport::canSend, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
&settings);
_pCommsCore = &commsCore;
}
// Called by the transport when bytes arrive
void onRxBytes(const uint8_t* p, uint32_t len)
{
if (_pCommsCore)
_pCommsCore->inboundHandleMsg(_channelID, p, len);
}
// Called by CommsChannelManager when it has bytes to send out
bool sendBytes(CommsChannelMsg& msg)
{
// Push msg.getBuf()/msg.getBufLen() out of your transport
return true;
}
bool canSend(uint32_t channelID, CommsMsgTypeCode msgType, bool& noConn)
{
noConn = !_isConnected;
return _isConnected && _txReady;
}
private:
uint32_t _channelID = CommsCoreIF::CHANNEL_ID_UNDEFINED;
CommsCoreIF* _pCommsCore = nullptr;
bool _isConnected = false;
bool _txReady = true;
};Real transports in the framework: BLEManager (registers a RICSerial channel over a GATT characteristic), SerialConsole (a channel sharing the console port with log text - see SerialConsole), CommandSerial (one RICSerial channel per UART), CommandSocket (TCP server, one channel per connection), and the WebServer's WebSockets (one channel per connection slot).
Defaults are defined in CommsChannelSettings.h:
| Field | Default | Meaning |
|---|---|---|
inboundBlockLen |
1200 | Expected per-message inbound block size in bytes. It is what inboundMsgBlockMax() returns, and RICREST file uploads on the channel use blocks of at most 2/3 of it, so 800 bytes by default |
inboundBlockLenMax |
5000 | Maximum inbound block size |
inboundQueueCountMax |
20 | Max number of queued inbound messages (one arriving when the queue is full is dropped and a warning logged) |
inboundQueueBytesMax |
20000 | Not currently enforced |
outboundBlockLen |
5000 | Per-message outbound block size |
outboundQueueMaxLen |
20 | Max queued outbound messages |
A value of 0 means the default. Pass a custom CommsChannelSettings to registerChannel() to tune per-channel queues and block sizes. For example, BLE uses its maxPktLen (500 by default) for the block sizes, so its uploads use blocks of up to 333 bytes, and a WebServer WebSocket on a board with PSRAM uses an inboundBlockLen of 24000 (blocks of up to 16000 bytes).
CommsChannelManager::loop() runs on the main task (the task running SysManager::loop()). On each pass, for each channel:
-
Outbound: it reads the type of the next queued message without copying it (
outboundQueuePeekType(), which usesThreadSafeQueue::peekWith()) and asks the channel'soutboundCanAcceptCBwhether that type can be sent. If it can, the message is moved out of the queue, encoded by the codec and passed tooutboundHandleMsgCB. If the callback reports no connection (noConn), the message is taken and discarded so the queue doesn't block. At most one outbound message per channel is sent per pass. -
Inbound: one queued block of received bytes is passed to the codec, if the codec is ready for more (
readyForRxData()).
Commands, responses and reports go through the outbound queue (outboundQueueMaxLen). Publish messages are not queued: outboundHandleMsg() sends a publish at once if the channel can accept it and drops it otherwise, and skips it if any other message is waiting in the channel's queue (unless IMPLEMENT_PUBLISH_EVEN_IF_OUTBOUND_QUEUE_NOT_EMPTY is defined). Command responses therefore aren't held up behind publishes.
Codecs are only created on the main task, and some transports' send paths (e.g. WebSockets) are main-task only, so code on another task that sends on a channel should hand the message to the main task first. Queue elements are held in PSRAM where available, and messages are moved through the queues rather than copied - see ThreadSafeQueue.
The decoded message container. Important fields/methods:
uint32_t getChannelID();
CommsMsgProtocol getProtocol(); // RICSerial, RICREST, BridgeRICREST, RawCmdFrame, ...
uint32_t getMsgNumber();
CommsMsgTypeCode getMsgTypeCode(); // COMMAND, RESPONSE, PUBLISH, REPORT
const uint8_t* getBuf();
uint32_t getBufLen();
void setAsResponse(const CommsChannelMsg& original); // copy channelID + msgNum
void setFromBuffer(uint32_t channelID, CommsMsgProtocol protocol,
uint32_t msgNum, CommsMsgTypeCode type,
const uint8_t* p, uint32_t len);CommsMsgProtocol values: MSG_PROTOCOL_ROSSERIAL, MSG_PROTOCOL_RICREST, MSG_PROTOCOL_BRIDGE_RICREST, MSG_PROTOCOL_RAWCMDFRAME, MSG_PROTOCOL_NONE.
A protocol codec deframes incoming bytes and frames outgoing messages. Codecs are added to the manager by passing a ProtocolCodecFactoryHelper to addProtocol():
ProtocolCodecFactoryHelper helper = {
ProtocolRICSerial::getProtocolNameStatic(), // "RICSerial"
ProtocolRICSerial::createInstance, // factory fn
configGetConfig(), // config source
"RICSerial", // config prefix
inboundFrameRxCB, // called with each decoded CommsChannelMsg
canAcceptInboundCB // flow-control predicate
};
commsCore.addProtocol(helper);ProtocolExchange registers the standard codec set (RICSerial, RICFrame, RICJSON) on behalf of the framework. Most application code does not need to add additional codecs.
| Codec | Wire framing | Typical use |
|---|---|---|
ProtocolRICSerial |
HDLC (MiniHDLC), with byte-stuffed FLAGs and CRC |
UART, BLE GATT, WebSocket (the default pcol) |
ProtocolRICFrame |
Two-byte header (msgNum, protocol/type) + payload |
TCP/sockets, where stream framing is provided by the transport |
ProtocolRICJSON |
Plain JSON, no overhead | JSON-only links |
ProtocolROSSerial |
ROS serial format | Legacy ROS bridge |
See RICREST Protocol for the inner-message format that all of these typically carry.
A bridge makes two channels exchange messages transparently. Typical use: an embedded peripheral on a serial port is reachable from a BLE-connected host without the host having to know how to talk to that peripheral.
uint32_t bridgeID = commsCore.bridgeRegister(
"myBridge",
establishmentChannelID, // the side that opened the bridge (e.g. BLE)
otherChannelID, // the side being bridged to (e.g. a serial peripheral)
/*idleCloseSecs*/ 30);
// ...
commsCore.bridgeUnregister(bridgeID, /*forceClose*/ false);While the bridge is open, RICREST messages can be wrapped in MSG_PROTOCOL_BRIDGE_RICREST envelopes (see CommsBridgeMsg); ProtocolExchange recognises these and forwards them via bridgeHandleInboundMsg() / bridgeHandleOutboundMsg().
CommsChannelManager exposes getInfoJSON() which returns a JSON list describing every registered channel — its ID, name, interface, protocol, and queue stats. This is useful from a SysMod's getDebugJSON() override.
Getting Started
- Quick Start
- Architecture at a Glance
- Writing Your First SysMod
- Adding a Comms Channel
- Adding an I2C Device Type
Scaffolding & Building
- Raft CLI
- Web Apps (Device Types, Flasher)
- SysTypes
- Top-Level SysType
- Build Process
- WebUI Build Pipeline
- File System
- Partitions & Flash
- Local Dev Libraries
- Library Developer Guide
Architecture
Built-in SysMods
- NetworkManager
- WiFi Troubleshooting
- BLEManager
- WebServer
- MQTTManager
- SerialConsole
- CommandSerial
- CommandSocket
- CommandFile
- FileManager
- LogManager
- ESPOTAUpdate
- StatePublisher
- Remote Logging
- Data Source Registration
Comms & Protocols
- Stack Overview
- Comms Channels
- ProtocolExchange
- RICREST Protocol
- Real-Time Streams
- Adding REST Endpoints
- Built-in REST Endpoints
- File Download (OKTO)
- OTA Update Flow
Devices & Buses
- DeviceManager
- Device Manager REST API
- Device Factory & Classes
- Device Type Records
- Adding an I2C Device Type
- Device Data Publishing
- Data Logger
- I2C Bus
- I2C Device Scanning
- I2C ID & Polling
- MotorControl Overview
- MotorControl Config
- MotorControl Commands
- Camera
ROS 2
Helpers
Reference