Skip to content

AddingACommsChannel

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

Adding a Comms Channel

A walkthrough for writing a SysMod that exposes a transport (a UART, a custom radio, a virtual loopback, …) as a Raft Comms Channel. Once registered, the same REST endpoints and publish/subscribe machinery that work over BLE and HTTP work over your transport with no extra code.

Read Communications Stack Overview first if you have not already.

What you will build

A LoopbackTransport SysMod that:

  • Registers a single channel using the RICSerial codec.
  • Echoes every framed message back to the sender.

You can use this pattern as the skeleton for a real transport — replace the echo with code that pushes bytes to your hardware.

1. Header

#pragma once

#include "RaftSysMod.h"
#include "CommsCoreIF.h"
#include "CommsChannelMsg.h"

class LoopbackTransport : public RaftSysMod
{
public:
    LoopbackTransport(const char* pModuleName, RaftJsonIF& sysConfig)
        : RaftSysMod(pModuleName, sysConfig) {}

    static RaftSysMod* create(const char* pModuleName, RaftJsonIF& sysConfig)
        { return new LoopbackTransport(pModuleName, sysConfig); }

protected:
    virtual void setup() override final {}
    virtual void loop() override final {}
    virtual void addCommsChannels(CommsCoreIF& commsCore) override final;

private:
    bool sendMsg(CommsChannelMsg& msg);
    bool canAccept(uint32_t channelID, CommsMsgTypeCode msgType, bool& noConn);

    CommsCoreIF* _pCommsCore = nullptr;
    uint32_t _channelID = CommsCoreIF::CHANNEL_ID_UNDEFINED;

    static constexpr const char* MODULE_PREFIX = "Loopback";
};

2. Register the channel

Channel registration must happen in addCommsChannels() — at this point the manager has all codecs available and other SysMods are also registering their channels.

void LoopbackTransport::addCommsChannels(CommsCoreIF& commsCore)
{
    _pCommsCore = &commsCore;

    static const CommsChannelSettings settings;   // defaults are usually fine

    _channelID = commsCore.registerChannel(
        /*protocolName  */ "RICSerial",
        /*interfaceName */ "Loopback",
        /*channelName   */ "Loopback",
        /*outboundCB    */ std::bind(&LoopbackTransport::sendMsg, this, std::placeholders::_1),
        /*canAcceptCB   */ std::bind(&LoopbackTransport::canAccept, this,
                                     std::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
        &settings);

    LOG_I(MODULE_PREFIX, "registered channel id %u", _channelID);
}

registerChannel() returns the new channel ID. Hold on to it — you will pass it to inboundHandleMsg() whenever you have bytes from the wire.

3. Outbound — push bytes out of the device

sendMsg() is invoked by the manager once the codec has framed an outbound message. You have to deliver msg.getBuf() / msg.getBufLen() to your hardware:

bool LoopbackTransport::sendMsg(CommsChannelMsg& msg)
{
    // For a real transport, hand these bytes to the driver:
    //   _serialDriver.write(msg.getBuf(), msg.getBufLen());
    // For the loopback example, feed them straight back into the inbound path:
    if (_pCommsCore)
        _pCommsCore->inboundHandleMsg(_channelID, msg.getBuf(), msg.getBufLen());
    return true;
}

bool LoopbackTransport::canAccept(uint32_t /*channelID*/, CommsMsgTypeCode /*type*/, bool& noConn)
{
    noConn = false;       // set true if your transport has lost its peer
    return true;
}

4. Inbound — push bytes into the channel

In a real transport, whenever bytes arrive from your hardware (an ISR, a task, a callback) call:

_pCommsCore->inboundHandleMsg(_channelID, pBytes, len);

The framework deframes them via the codec, dispatches to ProtocolExchange, and invokes the matching REST endpoint.

5. Register and configure

In main.cpp:

raftApp.getSysManager().registerSysMod("Loopback", LoopbackTransport::create);

In your SysType:

{ "name": "Loopback" }

6. Try it

Use any local SysMod that issues outbound messages back through the channel — for example, send a request via the subscription endpoint with MSG_CHANNEL_ID_ALL to hit every channel — or write a small test SysMod that calls outboundHandleMsg() with the channel ID.

Choosing a protocol codec

Codec When to use it
RICSerial Byte stream with no inherent message boundaries (UART, BLE GATT). HDLC-frames each message.
RICFrame Transport already provides framing (TCP, websocket binary). Two-byte header + payload.
RICJSON JSON-only links — readable, but no binary support.

See RICREST Protocol for the wire formats.

Tuning per-channel queues

Pass a custom CommsChannelSettings if your transport's MTU or latency differs from the defaults:

static const CommsChannelSettings settings(
    /*inboundBlockLen   */  256,
    /*inboundBlockMax   */ 1024,
    /*inboundQueueLen   */    8,
    /*inboundQueueBytes */ 8000,
    /*outboundBlockLen  */ 1024,
    /*outboundQueueLen  */   16);

See Comms Channels for the field meanings.

Common mistakes

  • Calling registerChannel() from setup() instead of addCommsChannels(). addCommsChannels() is called after every SysMod has finished setup(), so all codecs are guaranteed to be registered.
  • Forgetting to set noConn correctly. The manager uses noConn to decide whether to drop a publish vs. retry — set it to true only when you genuinely have no peer.
  • Returning false from sendMsg. That tells the manager the message wasn't sent and may trigger backoff. Only return false for a hard failure.

Related

Clone this wiki locally