12.7. Channel callbacks#
The backend object handed to protocol.register() is a Python class. The protocol library doesn’t ask the class which methods it implements; it inspects the instance and wires the ones it finds. That introspection is what makes the backend interface flexible: the smallest useful backend is two methods, the most elaborate is twelve, and the application opts in to each capability one method at a time.
12.7.1. The introspection rules#
When protocol.register() runs, the library walks a fixed list of callable names and binds each one it finds on the backend instance:
Adding
readto the class flipsCHANNEL_FLAG_READon. A host call tochannel_read()only reaches the backend if this flag is set.Adding
writeflipsCHANNEL_FLAG_WRITEon, enablingchannel_write().Adding
lockandunlockflipsCHANNEL_FLAG_LOCKon, enabling the host to lock the channel for a multi-packet atomic read.Adding
polllets the host ask “is anything ready?” cheaply, without forcing a full read.
Missing methods are not errors – the protocol library just leaves the corresponding capability disabled. A backend with only size and read is perfectly valid; it’s a read-only data channel.
12.7.2. A read-only sensor channel#
A sensor channel that publishes a fresh reading every time the host asks, refusing host writes, exercises four of the callbacks:
import protocol
import struct
class TempChannel:
def __init__(self, read_sensor):
self._read_sensor = read_sensor
self._buf = b''
self._fresh = False
def poll(self):
# Tell the host whether a reading is waiting.
return self._fresh
def size(self):
# Sample fresh data on every host-side size query.
value = self._read_sensor()
self._buf = struct.pack('<f', value)
self._fresh = True
return len(self._buf)
def read(self, offset, size):
end = offset + size
if end >= len(self._buf):
self._fresh = False
return self._buf[offset:end]
protocol.register(name='temp', backend=TempChannel(read_temperature))
Walking through what each method does:
pollreturns the freshness flag. The host calls it before reading and skips the read entirely when it returnsFalse. That saves the round-trip cost for “no new data yet.”sizeregenerates the buffer on demand and reports its length. Doing the sampling here means the backend doesn’t need a background task – a host call drives every measurement.readreturns a slice of the buffer. The protocol library may call it more than once when the buffer is larger than the negotiated max payload; theoffsetargument walks through the fragments.No
writemeans host writes are refused at the framing layer, before the backend is involved.
12.7.3. The full callback set#
For reference, every method the library looks for on a backend:
Method | Returns | Purpose |
|---|---|---|
| object | Optional one-shot initialisation when the channel first binds to a host. Return any non- |
| bool | Return |
| bool | Acquire the channel for an atomic multi-packet transfer. |
| bool | Release a prior |
| int | Number of bytes currently readable from the channel. |
| tuple | Up to four integers describing the data structure (e.g. image height, width, byte count). Used by the host to unpack typed buffers. |
| bytes | Return up to size bytes starting at offset. Called once per fragment when the payload exceeds the negotiated max. |
| bytes | Zero-copy variant of |
| int | Host wrote data at offset. |
| int | Application-defined opcode outside the read/write model. Negative return is an error. |
| object | Drop any buffered data. Called when the host wants to reset the channel. |
| bool | Only meaningful on backends that represent a physical transport (the built-in USB channels). Application channels don’t need this. |
That’s the entire backend interface. Twelve method names, all optional, and the protocol library decides what each channel can do based on which ones are present.