12.7. 通道回调#

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

12.7.1. 内省规则#

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

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

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

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

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

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

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 处写入了 data。data 是指向协议层接收缓冲区的 bytearray 视图——在返回之前请将你想保留的内容复制出来。

ioctl(self, cmd, length, arg)

int

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

flush(self)

对象

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

is_active(self)

bool

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

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