Skip to content

CommsChannels

Rob Dobson edited this page May 3, 2026 · 1 revision

Comms Channels

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.

Roles

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.

Channel lifecycle

  1. CommsChannelManager is constructed as a SysMod (typically by RaftCoreApp).
  2. Protocol codecs are added by SysMods in their addCommsChannels(CommsCoreIF&) override. The standard set (RICSerial, RICFrame, RICJSON) is added by ProtocolExchange.
  3. Transport SysMods register channels also from addCommsChannels(). Each call to registerChannel() returns a channelID.
  4. At runtime the manager allocates the actual codec object lazily the first time a channel needs to encode or decode.
  5. 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.
  6. Outbound: anything in the system calls outboundHandleMsg(CommsChannelMsg&); the manager finds the channel by ID, encodes via its codec and calls the channel's outboundHandleMsgCB.

CommsCoreIF API

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.

Special channel IDs

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

Registering a channel — example

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), CommandSerial (one RICSerial channel per UART), CommandSocket (TCP server, one channel per connection).

CommsChannelSettings

Defaults are defined in CommsChannelSettings.h:

Field Default Meaning
inboundBlockLen 1200 Expected per-message inbound block size in bytes
inboundBlockLenMax 5000 Maximum inbound block size
inboundQueueCountMax 20 Max number of queued inbound messages
inboundQueueBytesMax 20000 Max total bytes queued inbound
outboundBlockLen 5000 Per-message outbound block size
outboundQueueMaxLen 20 Max queued outbound messages

Pass a custom CommsChannelSettings to registerChannel() to tune per-channel queues — for example, BLE uses the GATT MTU as the block size to avoid fragmentation surprises.

CommsChannelMsg

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.

Protocol codecs

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
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.

Bridges

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().

Diagnostics

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.

Clone this wiki locally