-
Notifications
You must be signed in to change notification settings - Fork 3
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.
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), CommandSerial (one RICSerial channel per UART), CommandSocket (TCP server, one channel per connection).
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.
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 |
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
- 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