12.7. 通道回调

传递给 protocol.register() 的后端对象是一个 Python 类。协议库不会询问该类实现了哪些方法,而是检查实例并连接它所找到的方法。正是这种内省机制使得后端接口非常灵活:最小可用的后端只有两个方法,最完整的后端有十二个方法,应用程序每次按一个方法逐项启用各项能力。

12.7.1. 内省规则

protocol.register() 运行时,库会遍历一个固定的可调用名称列表,并绑定它在后端实例上找到的每一个:

  • 向类中添加 read 会开启 CHANNEL_FLAG_READ。只有设置了该标志,主机对 channel_read() 的调用才会到达后端。

  • 添加 write 会开启 CHANNEL_FLAG_WRITE,启用 channel_write()

  • 添加 lockunlock 会开启 CHANNEL_FLAG_LOCK,使主机能够锁定通道以进行多数据包原子读取。

  • 添加 poll 让主机能够廉价地询问“是否有数据就绪?”,而无需强制执行一次完整的读取。

缺少方法不是错误——协议库只是让相应的能力保持禁用状态。一个仅有 sizeread 的后端是完全有效的;它是一个只读数据通道。

12.7.2. 只读传感器通道

一个传感器通道在每次主机询问时发布一个新读数,并拒绝主机写入,它使用了四个回调:

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))

逐一说明每个方法的作用:

  • poll 返回新鲜度标志。主机在读取前调用它,当它返回 False 时完全跳过读取。这样就省去了“尚无新数据”时的往返开销。

  • size 按需重新生成缓冲区并报告其长度。在此处进行采样意味着后端不需要后台任务——每次测量都由一次主机调用来驱动。

  • read 返回缓冲区的一个切片。当缓冲区大于协商的最大有效载荷时,协议库可能会多次调用它;offset 参数用于遍历各个分片。

  • 没有 write 意味着主机写入会在分帧层就被拒绝,根本不会涉及后端。

12.7.3. 完整的回调集合

供参考,库会在后端上查找的每一个方法:

方法

返回值

用途

init(self)

对象

当通道首次绑定到主机时可选的一次性初始化。成功时返回任何非 None 值。

poll(self)

bool

当有数据可用时返回 True

lock(self)

bool

获取通道以进行原子的多数据包传输。

unlock(self)

bool

释放先前的 lock

size(self)

int

当前可从通道读取的字节数。

shape(self)

tuple

最多四个整数,用于描述数据结构(例如图像高度、宽度、字节数)。主机用它来解包带类型的缓冲区。

read(self, offset, size)

bytes

offset 开始返回最多 size 个字节。当有效载荷超过协商的最大值时,每个分片调用一次。

readp(self, offset, size)

bytes

read 的零拷贝变体:缓冲区的内存必须在传输期间保持有效。

write(self, offset, data)

int

主机在 offset 处写入了 datadata 是指向协议层接收缓冲区的 bytearray 视图——在返回之前请将你想保留的内容复制出来。

ioctl(self, cmd, length, arg)

int

读/写模型之外的应用程序自定义操作码。返回负值表示错误。

flush(self)

对象

丢弃所有缓冲的数据。当主机想要重置通道时调用。

is_active(self)

bool

仅对表示物理传输的后端(内置的 USB 通道)有意义。应用通道不需要它。

这就是整个后端接口。十二个方法名,全部可选,协议库根据存在哪些方法来决定每个通道能做什么。