15.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.
15.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.
15.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.
15.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.