-
Notifications
You must be signed in to change notification settings - Fork 3
AddingACommsChannel
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.
A LoopbackTransport SysMod that:
- Registers a single channel using the
RICSerialcodec. - 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.
#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";
};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.
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;
}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.
In main.cpp:
raftApp.getSysManager().registerSysMod("Loopback", LoopbackTransport::create);In your SysType:
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.
| 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.
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.
-
Calling
registerChannel()fromsetup()instead ofaddCommsChannels().addCommsChannels()is called after every SysMod has finishedsetup(), so all codecs are guaranteed to be registered. -
Forgetting to set
noConncorrectly. The manager usesnoConnto decide whether to drop a publish vs. retry — set it totrueonly when you genuinely have no peer. -
Returning
falsefromsendMsg. That tells the manager the message wasn't sent and may trigger backoff. Only returnfalsefor a hard failure.
Getting Started
- Quick Start
- Architecture at a Glance
- Writing Your First SysMod
- Adding a Comms Channel
- Adding an I2C Device Type
- PlatformIO / Arduino
Scaffolding & Building
- Raft CLI
- SysTypes
- Top-Level SysType
- Build Process
- WebUI Build Pipeline
- File System
- Partitions & Flash
- Local Dev Libraries
- Library Developer Guide
Architecture
Built-in SysMods
- NetworkManager
- 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
Helpers
Reference
{ "name": "Loopback" }