Skip to content

RealTimeStreams

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

Real-Time Streams

Real-time streams are open-ended RICREST file/stream sessions for low-latency bidirectional byte streams over BLE, WebSocket, serial, or other comms channels.

Protocol flow

A client opens a stream with a ufStart command frame using fileType: "rtstream":

{
  "cmdName": "ufStart",
  "reqStr": "ufStart",
  "fileType": "rtstream",
  "fileName": "upyconsole",
  "endpoint": "upyconsole",
  "fileLen": 0
}

The device allocates a FileStreamSession and responds with a non-zero streamID:

{
  "req": "ufStart",
  "rslt": "ok",
  "streamID": 1
}

Data is then carried in RICREST FILEBLOCK frames. Each block uses the standard file/stream header:

[streamID:1 byte][filePos:3 bytes big-endian][payload:N bytes]

The encoded position word is:

(streamID << 24) | (filePos & 0x00ffffff)

The streamID must match the non-zero ID returned by ufStart. The low 24 bits are the stream position for that session, so stream senders should wrap or resynchronize at the protocol layer if they run beyond that range.

The client closes the stream with ufEnd:

{
  "cmdName": "ufEnd",
  "reqStr": "ufEnd",
  "streamID": 1
}

Endpoint routing

ProtocolExchange creates a FileStreamSession with content type realTimeStream. The session looks up the REST endpoint named by the endpoint field and delivers each block to that endpoint's chunk callback.

The chunk callback receives a request string shaped like:

<endpoint>?streamID=<streamID>&fileName=<fileName>

For example:

upyconsole?streamID=1&fileName=upyconsole

The block data is passed as a FileStreamBlock. Its firstBlock flag is true for the first accepted block in a stream, and finalBlock is true only when a finite-length stream reaches fileLen. Open-ended streams normally close with ufEnd rather than a final data block.

Flow control

Real-time streams use StreamDatagramProtocol. Blocks are accepted when their filePos is at or ahead of the current stream position, which suits low-latency streams where late data is less useful than current data.

If the receiver is busy or sees a position mismatch, it may respond with sokto, indicating the stream position accepted so far:

{
  "rslt": "ok",
  "streamID": 1,
  "sokto": 1024,
  "reason": "RAFT_BUSY"
}

Clients should treat sokto as back-pressure or resynchronization feedback. Bulk file transfers should use the OKTO upload/download protocols instead.

raftjs client API

raftjs exposes open-ended real-time streams through RaftConnector.openRtStream():

const handle = await connector.openRtStream({
  fileName: "upyconsole",
  endpoint: "upyconsole",
  onData: (data, filePos, streamID) => {
    // Handle inbound bytes from the device.
  },
});

await handle.sendText("print('hello')\r");
await handle.close();

The returned handle includes:

Field Meaning
streamID Non-zero stream session ID allocated by the device
maxBlockSize Maximum block size reported by the device, or the client default
sendBytes(bytes) Send a binary block
sendText(text) UTF-8 encode and send text
close() Send ufEnd and remove the callback

RaftMicroPythonConsoleClient builds on this API for MicroPython REPL traffic, using fileName: "upyconsole" and endpoint: "upyconsole".

For finite real-time payloads, RaftConnector.streamData() and RaftStreamHandler use the same rtstream session type but send a known fileLen and complete with ufEnd after all blocks are sent.

Related

Clone this wiki locally