物联网RPC框架Python DCOM

欢迎前往社区交流:海萤物联网社区

简介

RPC:Remote Procedure Call,远程过程调用。使用RPC可以让一台计算机的程序程调用另一台计算机的上的程序。

RPC通过把网络通讯抽象为远程的过程调用,调用远程的过程就像调用本地的子程序一样方便,从而屏蔽了通讯复杂性,使开发人员可以无需关注网络编程的细节,将更多的时间和精力放在业务逻辑本身的实现上,提高工作效率。

DCOM:Device Communication Protocol(DCOM),设备间通信协议。DCOM是针对物联网使用场景开发的RPC框架,主要有如下特点:

  • 协议开销极小仅4字节。物联网很多场景都是几十字节的短帧,RPC协议本身的开销如果过大会导致在这些场景无法应用
  • 可以跨语言通信。DCOM协议在设计上与语言无关,无论C,Golang,Python等都可以使用DCOM
  • 可以跨通信介质通信。DCOM协议可以工作在以太网,串口,wifi,小无线等一切通信介质之上

在海萤物联网中,节点间使用DCOM来通信。本文介绍Python语言开发的DCOM包的使用方法。

开源

安装

pip install dcompy

基础概念

查看文档 海萤物联网教程:Go SDK 基础概念章节。

API

def load(param: LoadParam)
	"""模块载入"""

def register(protocol: int, rid: int, callback):
    """
    注册DCOM服务回调函数
    :param protocol: 协议号
    :param rid: 服务号
    :param callback: 回调函数.格式: func(pipe: int, src_ia: int, req: bytearray) (bytearray, int)
    :return: 返回值是应答和错误码.错误码为0表示回调成功,否则是错误码
    """

def receive(protocol: int, pipe: int, src_ia: int, data: bytearray):
    """
    接收数据.应用模块接收到数据后需调用本函数,本函数接收帧的格式为DCOM协议数据
    """

def register(protocol: int, rid: int, callback):
    """
    注册DCOM服务回调函数
    :param protocol: 协议号
    :param rid: 服务号
    :param callback: 回调函数.格式: func(pipe: int, src_ia: int, req: bytearray) (bytearray, int)
    :return: 返回值是应答和错误码.错误码为0表示回调成功,否则是错误码
    """

def call(protocol: int, pipe: int, dst_ia: int, rid: int, timeout: int, req: bytearray) -> (bytearray, int):
    """
    RPC同步调用
    :param protocol: 协议号
    :param pipe: 通信管道
    :param dst_ia: 目标ia地址
    :param rid: 服务号
    :param timeout: 超时时间,单位:ms.为0表示不需要应答
    :param req: 请求数据.无数据可填bytearray()或者None
    :return: 返回值是应答字节流和错误码.错误码非SYSTEM_OK表示调用失败
    """
  • 数据结构
    class LoadParam:
      """
      载入参数
      """
    
      def __init__(self):
          # 块传输帧重试间隔.单位:ms
          self.block_retry_interval = 0
          # 块传输帧重试最大次数
          self.block_retry_max_num = 0
    
          # API接口
          # 是否允许发送.函数原型:func(pipe: int) bool
          self.is_allow_send = None  # type: Callable[[int], bool]
          # 发送的是DCOM协议数据.函数原型:func(protocol: int, pipe: int, dst_ia: int, bytes: bytearray)
          self.send = None  # type: Callable[[int, int, int, bytearray], None]
    
  • 系统错误码
    # 系统错误码
    # 正确值
    SYSTEM_OK = 0
    # 接收超时
    SYSTEM_ERROR_RX_TIMEOUT = 0x10
    # 发送超时
    SYSTEM_ERROR_TX_TIMEOUT = 0x11
    # 内存不足
    SYSTEM_ERROR_NOT_ENOUGH_MEMORY = 0x12
    # 没有对应的资源ID
    SYSTEM_ERROR_INVALID_RID = 0x13
    # 块传输校验错误
    SYSTEM_ERROR_WRONG_BLOCK_CHECK = 0x14
    # 块传输偏移地址错误
    SYSTEM_ERROR_WRONG_BLOCK_OFFSET = 0x15
    # 参数错误
    SYSTEM_ERROR_PARAM_INVALID = 0x16
    

load:模块载入

在使用DCOM前必须要初始化。DCOM支持重传,所以在初始化时需输入重传间隔以及重传最大次数。

DCOM与通信介质无关,不同介质可定义不同的管道号。应用程序需要在是否允许发送函数(is_allow_send),以及发送函数(send)中编写不同管道的操作。

  • 示例:某节点有两个管道 ```python if name == ‘main': param = dcompy.LoadParam() param.block_retry_max_num = 5 param.block_retry_interval = 1000 param.is_allow_send = is_allow_send param.send = send dcompy.load(param)

def is_allow_send(pipe: int) -> bool: if pipe == 1: return is_pipe1_allow_send() else: return is_pipe2_allow_send()

def send(protocol: int, pipe: int, dst_ia: int, data: bytearray): if pipe == 1: pipe1_send(data) else: pipe2_send(data)


protocol,dst_ia等字段根据需求处理。

### receive 接收数据
应用程序接收到数据需要调用receive函数,将数据发送给DCOM。

- 示例:某节点有两个管道都可接收
```python
def pipe1_receive(data: bytearray):
	dcom.receive(0, 1, 0x2140000000000101, data)

def pipe2_receive(data: bytearray):
	dcom.Receive(0, 2, 0x2140000000000101, data)

协议号protocol示例中填写的是0,应用时根据实际场景填写。

register:服务注册

节点可以通过注册服务开放自身的能力。

  • 示例:假设节点2140::101是智能插座,提供控制和读取开关状态两个服务:
dcom.register(0, 1, control_service)
dcom.register(0, 2, get_state_service)

// control_service控制开关服务
// 返回值是应答和错误码.错误码为0表示回调成功,否则是错误码
def control_service(pipe: int, src_ia: int, req: bytearray) -> (bytearray, int):
	if req[0] == 0:
		off()
	else:
		on()
	return None, dcom.SystemOK

// get_state_service 读取开关状态服务
// 返回值是应答和错误码.错误码为0表示回调成功,否则是错误码
def get_state_service(pipe: int, src_ia: int, req: bytearray) -> (bytearray, int):
	return bytearray([state()]), dcom.SystemOK

call:同步调用

def call(protocol: int, pipe: int, dst_ia: int, rid: int, timeout: int, req: bytearray) -> (bytearray, int):
    """
    RPC同步调用
    :param protocol: 协议号
    :param pipe: 通信管道
    :param dst_ia: 目标ia地址
    :param rid: 服务号
    :param timeout: 超时时间,单位:ms.为0表示不需要应答
    :param req: 请求数据.无数据可填bytearray()或者None
    :return: 返回值是应答字节流和错误码.错误码非SYSTEM_OK表示调用失败
    """

同步调用会在获取到结果之前阻塞。节点可以通过同步调用,调用目标节点的函数或者服务。timeout字段是超时时间,单位是毫秒。如果目标节点超时未回复,则会调用失败。如果超时时间填0,则表示不需要目标节点回复。

  • 示例:2141::102节点控制智能插座2141::101开关状态为开
resp, errCode = dcom.call(0, 1, 0x2140000000000101, 3000, bytearray([1]))
  • 示例:2141::102节点读取智能插座2141::101开关状态
resp, errCode = dcom.call(0, 2, 0x2140000000000101, 3000, None)
if errCode == dcom.SystemOK:
	print("开关状态:", resp[0])

请求和应答数据格式

DCOM通信双方发送的数据流都是二进制,请求(req)和应答(resp)的数据类型都是[]uint8。

二进制不利于应用处理,所以会将二进制转换为其他数据类型来处理。常用的有以下三种:

  • 结构体
  • json
  • 字符串

在物联网中,硬件节点的资源有限,且大部分都是使用C语言编写代码。所以建议使用C语言结构体来通信。结构体约定使用1字节对齐,小端模式。

海萤物联网提供sbc库用来进行C语言结构体和二进制流的转换,详情可以查看文档:海萤物联网教程:sbc:基于python的C语言格式结构体和二进制转换库