rpc --- rpc 库

OpenMV Cam 上的 rpc 模块允许你将 OpenMV Cam 连接到另一个微控制器或计算机,并在 OpenMV Cam 上执行远程 python(或过程)调用。如果你希望让 OpenMV Cam 能够在另一个微控制器或计算机上执行远程过程(或 python)调用,rpc 模块也支持反向操作。

如何使用本库

一个通过 UART 暴露单个回调的最小 从机

import rpc
import csi

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

interface = rpc.rpc_uart_slave(baudrate=115200)

def snapshot(_):
    return csi0.snapshot().compress().bytearray()

interface.register_callback(snapshot)
interface.loop()  # Does not return.

向从机请求一帧 JPEG 图像的对应 主机

import rpc

interface = rpc.rpc_uart_master(baudrate=115200)

result = interface.call("snapshot")
if result is None:
    print("communication failed")
elif len(result) == 0:
    print("remote function not registered on the slave")
else:
    # result is a memoryview of the JPEG bytes returned by the slave.
    print("received", len(result), "bytes")

rpc_uart_master / rpc_uart_slave 替换为对应的 cani2cspi 配对即可使用其他传输方式。

通常,控制器设备要使用 rpc 库时,你需要用 rpc 库创建一个接口对象。例如:

interface = rpc.rpc_uart_master(baudrate=115200)

这将创建一个用于与 rpc 从机通信的 UART 接口。

接口创建完成后,你只需执行:

memory_view_object_result = interface.call("remote_function_or_method_name", bytes_object_argument)

随后 rpc 库会尝试在从机上执行该 "remote_function_or_method_name"。远程函数或方法将接收 bytes_object_argument,其大小最多可达 2^32-1 字节。远程方法执行完成后会返回 memory_view_object_result,其大小同样最多可达 2^32-1 字节。由于参数和响应都是通用的字节容器,你可以通过 rpc 库传递任意内容并接收任意类型的响应。传递参数的一种简单方法是使用 struct.pack() 创建参数,并在另一端使用 struct.unpack() 接收参数。对于响应,另一端可以发送字符串对象或 json 字符串作为结果,主机随后可对其进行解析。

关于错误处理,如果你尝试执行一个不存在的函数或方法名,rpc_master.call() 方法将返回一个空的 bytes() 对象。如果 rpc 库未能与从机通信,rpc 库将返回 None。

为保持简单,rpc 库不会在主机与从机设备之间维持连接。rpc_master.call() 方法封装了尝试连接从机、启动远程函数或方法的执行以及获取结果的全过程。

现在,在从机这一侧,你需要创建一个 rpc 接口来与主机通信。其形式如下:

interface = rpc.rpc_uart_slave(baudrate=115200)

这将创建用于与 rpc 主机通信的 UART 接口层。

创建从机接口后,你需要通过该接口对象注册主机可以调用的回调:

def remote_function_or_method_name(memoryview_object_argument):
    <lots of code>
    return bytes_object_result

interface.register_callback(remote_function_or_method_name)

你可以在从机上注册任意数量的回调。最后,注册完所有回调后,你只需执行:

interface.loop()

在从机上启动 rpc 库并开始监听主机。请注意,rpc_slave.loop() 方法不会返回。

class rpc -- rpc 基类

rpc 基类由 rpc_masterrpc_slave 类重新实现以创建主机和从机接口。它不应被直接使用。

class rpc.rpc

创建一个 rpc 对象。不应被直接使用。

get_bytes(buff: bytearray | memoryview, timeout_ms: int) bytes | None

由特定传输方式的子类重新实现。在 timeout_ms 毫秒内用来自底层接口的字节填充 buff。超时时返回 None

put_bytes(data: bytes | memoryview, timeout_ms: int) None

由特定传输方式的子类重新实现。在 timeout_ms 毫秒内通过底层接口发送 data

stream_reader(call_back: Callable[[memoryview], None], queue_depth: int = 1, read_timeout_ms: int = 5000) None

从远程 rpc.stream_writer 接收一连串负载数据。应在双方完成同步后,从 rpc_slave 回调内部(或在 rpc_master.call 成功后直接)调用。

  • call_back —— 每收到一个负载就以 call_back(data) 形式调用一次的可调用对象,其中 datamemoryview。返回值会被忽略。

  • queue_depth —— 写入端在等待读取端之前允许发送的传输中帧数。较高的值会以内存为代价提高吞吐量。

  • read_timeout_ms —— 每个负载的等待毫秒数。

发生任何错误时返回。要取消,可在 call_back 内部抛出异常;远程端将会超时。

stream_writer(call_back: Callable[[], bytes | memoryview], write_timeout_ms: int = 5000) None

向远程 rpc.stream_reader 发送一连串负载数据。应在双方完成同步后,从 rpc_slave 回调内部(或在 rpc_master.call 成功后直接)调用。

  • call_back —— 不带参数调用的可调用对象,返回下一个要发送的 bytesmemoryview 负载。

  • write_timeout_ms —— 发送每个负载时的等待毫秒数。

发生任何错误时返回。要取消,可在 call_back 内部抛出异常;远程端将会超时。

class rpc_master -- rpc_master 基类

rpc_master 是一个基类。请使用特定传输方式的子类之一(rpc_can_masterrpc_i2c_masterrpc_spi_masterrpc_uart_master)。

class rpc.rpc_master

创建一个 rpc_master 对象。不应被直接使用。

call(name: str, data: bytes = bytes(), send_timeout: int = 1000, recv_timeout: int = 1000) memoryview | None

在从机设备上执行一次远程调用。

  • name —— 要执行的远程函数或方法的字符串名称。

  • data —— 作为参数传递给远程函数的 bytes 类对象。

  • send_timeout —— 在连接从机并启动远程函数执行期间的等待毫秒数。一旦主机开始发送参数,此值便不再适用;该库允许参数传输最多耗时 5 秒。

  • recv_timeout —— 等待从机开始返回响应的毫秒数。一旦主机开始接收响应,此值便不再适用;该库允许响应传输最多耗时 5 秒。

成功时返回响应的 memoryview;如果远程名称在从机上不存在,则返回空的 bytes();通信失败时返回 None

class rpc_slave -- rpc_slave 基类

rpc_slave 是一个基类。请使用特定传输方式的子类之一(rpc_can_slaverpc_i2c_slaverpc_spi_slaverpc_uart_slave)。

class rpc.rpc_slave

创建一个 rpc_slave 对象。不应被直接使用。

register_callback(cb: Callable[[memoryview], bytes | memoryview]) None

注册一个主机可以按名称调用的回调。cb 是一个接受一个 memoryview 参数并返回 bytes 类对象的可调用对象。该回调的 __name__ 被用作查找键。

schedule_callback(cb: Callable[[], None]) None

cb(一个不带参数的可调用对象)安排为执行一次,时机为当前正在运行的 rpc 回调成功将其响应返回给主机之后立即执行。必须从 rpc 回调内部调用。这允许长时间运行的工作或 rpc.get_bytes/rpc.put_bytes 直通传输在两次 rpc 事务之间运行。如需重复执行,请在每次调用时重新注册。

setup_loop_callback(cb: Callable[[], None]) None

注册 cb(一个不带参数的可调用对象),使其在 rpc_slave.loop 的每次迭代时被调用。与 rpc_slave.schedule_callback 不同,此回调会保持注册状态。它必须是非阻塞的;调用频率是可变的。

loop(recv_timeout: int = 1000, send_timeout: int = 1000) None

运行 rpc 从机分发循环。除非回调抛出异常,否则不会返回。

  • recv_timeout —— 在重试之前等待主机命令的毫秒数。

  • send_timeout —— 在返回接收之前等待主机确认响应的毫秒数。

class rpc_can_master -- CAN 主机接口

通过 CAN 控制另一个 rpc 设备。

class rpc.rpc_can_master(message_id: int = 0x7FF, bit_rate: int = 250000, sample_point: float = 75, can_bus: int = 2)
  • message_id —— 用于数据传输的 11 位 CAN 消息 id。

  • bit_rate —— 以比特每秒为单位的 CAN 比特率。

  • sample_point —— Tseg1/Tseg2 采样点百分比(例如 50.0、62.5、75、87.5)。

  • can_bus —— CAN 外设编号。

主机和从机的 message_idbit_rate 必须匹配。总线必须用 120 欧姆电阻端接。

class rpc_can_slave -- CAN 从机接口

通过 CAN 受另一个 rpc 设备控制。

class rpc.rpc_can_slave(message_id: int = 0x7FF, bit_rate: int = 250000, sample_point: float = 75, can_bus: int = 2)

参数说明请参见 rpc_can_master

class rpc_i2c_master -- I2C 主机接口

通过 I2C 控制另一个 rpc 设备。

class rpc.rpc_i2c_master(slave_addr: int = 0x12, rate: int = 100000, i2c_bus: int = 2)
  • slave_addr —— 从机设备的 7 位 I2C 地址。

  • rate —— 以 Hz 为单位的 I2C 总线时钟频率。

  • i2c_bus —— I2C 外设编号。

主机和从机地址必须匹配。SCL 和 SDA 上需要外部上拉电阻,且两个设备必须共地。

class rpc_i2c_slave -- I2C 从机接口

通过 I2C 受另一个 rpc 设备控制。

class rpc.rpc_i2c_slave(slave_addr: int = 0x12, i2c_bus: int = 2)
  • slave_addr —— 此从机响应的 7 位 I2C 地址。

  • i2c_bus —— I2C 外设编号。

class rpc_spi_master -- SPI 主机接口

通过 SPI 控制另一个 rpc 设备。

class rpc.rpc_spi_master(cs_pin: str = 'P3', freq: int = 1000000, clk_polarity: int = 1, clk_phase: int = 0, spi_bus: int = 2)
  • cs_pin —— 片选引脚名称。

  • freq —— 以 Hz 为单位的 SPI 总线时钟频率。

  • clk_polarity —— 空闲时的时钟电平(0 或 1)。

  • clk_phase —— 在第一个(0)或第二个(1)时钟边沿采样数据。

  • spi_bus —— SPI 外设编号。

主机和从机设置必须匹配。将 CS、SCLK、MOSI、MISO 直接连接。两个设备必须共地。

class rpc_spi_slave -- SPI 从机接口

通过 SPI 受另一个 rpc 设备控制。

class rpc.rpc_spi_slave(cs_pin: str = 'P3', clk_polarity: int = 1, clk_phase: int = 0, spi_bus: int = 2)
  • cs_pin —— 片选输入引脚名称。

  • clk_polarity —— 空闲时的时钟电平(0 或 1)。

  • clk_phase —— 在第一个(0)或第二个(1)时钟边沿采样数据。

  • spi_bus —— SPI 外设编号。

class rpc_uart_master -- UART 主机接口

通过异步串口(UART)控制另一个 rpc 设备。

class rpc.rpc_uart_master(baudrate: int = 9600, uart_port: int = 3)
  • baudrate —— 串口波特率。

  • uart_port —— UART 外设编号。

主机和从机的波特率必须匹配。将主机 TX 连接到从机 RX,主机 RX 连接到从机 TX。两个设备必须共地。

class rpc_uart_slave -- UART 从机接口

通过异步串口(UART)受另一个 rpc 设备控制。

class rpc.rpc_uart_slave(baudrate: int = 9600, uart_port: int = 3)
  • baudrate —— 串口波特率。

  • uart_port —— UART 外设编号。