-
Notifications
You must be signed in to change notification settings - Fork 33
Chinese documents
本文主要介绍Pando协议的整体构成,包括设备与服务器的认证和接入流程,协议数据包的构成,以及通讯过程中数据成员的定义。
命令:控制指令,用于操作硬件层设备,比如控制开关打开或关闭
事件:
数据:
子设备:sub device,通常指(但不仅限)硬件层的控制器,也可以指软件虚拟的硬件层设备。
网关: gateway,通常指接入设备,用于接入internet。
服务器: 特指云端服务器。
载荷: Payload,指数据包中待传输的那部分真正有用的信息
- pando协议定义了一套统一的认证和接入流程,不同的设备只要遵循这套流程,就能接入云端服务器进行业务通信。
- pando协议定义了统一业务数据包封装方式,但没有规定具体指令。
- pando协议可使用不同的通信协议进行数据传输。只要服务器端和设备端使用相同的通信协议即可完成业务数据的传输与解析。
现在协议采取了“MQTT协议”传输业务数据包,MQTT协议是现阶段比较合适物联网的协议方案,可以在低带宽和不稳定的网络条件下保证通信的稳定。
另外也可以采用COAP协议、自定义二进制传输协议,可根据不同的场景选择通信协议。
协议分为三层,如下图所示
接入层分为云服务器和接入设备,其中接入设备的形态不一,可以是wifi、3G等,主要负责连接internet将数据包转发至云端服务器。
硬件层负责将传感器的信息传递至接入层的接入设备,数据传递的方式有串口、SPI、USB、蓝牙等。
本节介绍设备在已连网的情况下,如何与服务器完成注册、登陆认证、接入传输数据等流程。
总体流程示意如下图所示
开始所有流程前,必须保证设备已接入internet。
- 查看本地配置,是否有device id以及device key配置。如果有,则设备已经注册成功了,退出注册流程;否则继续。
- 获取设备的vendor key和product key。详见如何配置厂商设备信息。
- 通过程序获取当前网关类型的设备对应的唯一硬件标识符(如mac地址, imei码等)。
- 以vendor key, product key, 标识符, 设备类型, 版本等为参数,通过http api向服务器注册设备,等待服务器注册结果。
- 服务器将结果返回给网关。如果注册成功,则会返回一个device id和device key。网关保存下来作为登陆的设备id和密钥;如果注册失败,回到步骤4一直重试,直到成功为止。
- 查看本地配置,获取设备id及设备key。如果没有,返回至注册流程。
- 通过http api向服务器验证设备id和设备key。如果成功,服务器会返回device token以及access ip;如果服务器返回失败,说明设备id和key已过期,需要删除本地设备id和设备key,重新进入注册流程。如果是服务器不可用或者超时,则一直重试,直到成功为止。
- 接入流程的细节与通信协议有关, 设备以指定的通信协议连接至服务器(access ip)。
- 接入时必须上报device token和device id.
一方面,服务使用device id区分当前是哪个设备尝试接入;另一方面,服务器会进行校验device token和device id,通过校验后才能进行后续的信息交互。
接入流程中和服务器的任何数据流交互都需要对设备token进行校验(包括服务器和设备都需要对协议包中的token字段进行比对校验,如果不合法则应该丢弃)。 - 设备接入后,需要根据设备类型不同,按照一定频率持续向服务器发送心跳包(wifi模块默认每30s一次,手机终端默认4分钟一次)
- 在token过期的情况下,服务器会向设备发送“重新登陆”的指令,网关收到后重新进入登陆流程,获取新的token。
- 当连接断开时, 设备尝试重连失败后, 返回至登陆流程。
设备完成接入流程后,开始信息的交互,包括指令、数据、事件。
-
设备和硬件子设备之间
受硬件子设备性能所限,包括运算性能、内存、传输带宽等,这阶段使用较为紧凑的二进制数据包形式进行传输。
数据通过串口,蓝牙,I2C等接口传输,传输路径较短,数据丢失损坏的概率较小,因此没有涉及复杂的校验机制。
目前仅使用了crc校验确定数据的完整,采用sequence、ack的方式保证数据的可靠到达。 如下图所示,接收方需要回复ack包进行确认,要求回复的sequence与收到的一致。如果发送方没有收到ack,则重发数据包,尝试3次后不再重传。
指令、数据、事件拥有独立的sequence,每发一个包递增1,重传数据包时sequence不变。
实际情况还是需要一种简单,可移植,开销小的通信机制保证数据的完整性【待实现】。 -
设备和服务器之间
internet网络环境非常复杂,进行数据传输时,必须有多种机制保证数据传输的安全及完整性。
目前使用ssl连接、mqtt协议、token校验三种方式保证数据的可靠送达。
-
关键信息
sub device id: 子设备id。一个网关拥有多个子设备,id用于表示数据包归属于哪个子设备
command num,event num:指令编号、事件编号
property num: 数据的属性编号
tlv: type, length, value的缩写,type表示value的数据类型,length则表示vlaue(bytes, string这种无类型长度的数据)的具体长度。tlv保存了指令、事件、数据包属性的参数。 -
辅助信息
网关
device id:设备id,由服务器分配的唯一的id
timestamp:时间戳,由网关产生,指示数据包的生成时间
token:用于服务器和网关校验数据包的来源,保证安全性
payload type:表示数据包类型
payload length:表示数据包携带的关键信息的长度
硬件子设备
payload type:表示数据包类型
payload length:表示数据包携带的关键信息的长度
magic:魔数,位于包起始位置
crc:数据包校验和
frame sequence:数据包序号,用于重传
flag:用于未来协议扩展
了解数据包传输的两个阶段,以及数据成员的意义后,就明白网关需要对数据包进行转换。
如下图所示,数据包传输的过程中,网关进行二进制和mqtt数据包的转换。
这里要指出一点,接入设备与硬件层的设备,可以分别是两个实体设备,也可以只有一个实体设备,接入设备和硬件层的软件都在该设备上运行。例如:
- ESP8266作为接入设备,STM32作为硬件层设备,两者之间通过串口进行通信;
- ESP8266作为接入设备,同时自己又充当一个硬件层设备,通过GPIO控制外围接口(GPIO等);
- 选择3G模块作为接入internet的通道时,由于3G模块上无法运行自己的软件,只能通过AT指令进行通信。此时选择STM32作为硬件层设备,同时需要在上面运行接入设备的软件。
本节应该介绍协议如何封装信息,同时介绍数据头部应该携带哪些信息,这些信息在不同协议中应该如何处理
这部分要把通信协议相关的内容切出去。
不论数据包在哪一层,承载信息的数据包均分为"包头(header)"和"载荷(Payload)"两部分。
-
接入设备-服务器之间的数据包,结构入下图所示
-
终端-子设备之间的数据包,结构入下图所示
-
Payload
Command
Event
Data
tlv of payload
这部分主要描述二进制数据包的组成,及子设备数据包的结构,并简述MQTT包头中与设备相关的字段。
-
mqtt头部中与设备相关的字段
- 使用device id(每个接入设备的唯一标识符,8字节长)的16进制转换成字符串作为mqtt connect的client id,用于告知服务器哪个设备上线了,并确定了接入设备与服务器之间唯一的tcp连接。
如device id为0x0000 0A01,则client id为“A01”,前面不补0 - 数据包使用数据包类型(事件、命令、数据)作为topic。
在mqtt的topic采用"payload_type"表示,可取值“e”, “c”, “d”,不包含引号,分别表示event,command,data。
- 使用device id(每个接入设备的唯一标识符,8字节长)的16进制转换成字符串作为mqtt connect的client id,用于告知服务器哪个设备上线了,并确定了接入设备与服务器之间唯一的tcp连接。
-
二进制协议各字段的详细说明
此处以C语言为例,详细说明各字段的意义- 首先是服务器与接入设备间的header
struct mqtt_bin_header { uint8 flags; /* 标志位 */ uint64_t timestamp; /* 时间戳 */ uint8_t token[16]; /* token */ };
-
token是通过算法计算出来的校验令牌,用于防范数据包伪造,长度为16字节
-
flag用于一些特殊的业务,如文件传输,p2p,以及一些未来可能需要扩展的业务。
现在用到的场景是文件传输,当flag为1时,表示这个command携带了文件所在的uri,终端要下载此文件,并传输给子设备。
其余flag暂时保留业务 FLAG 文件传输 1
- 接入设备与硬件层设备之间的header
现在的子设备与终端之间没有一套类似MQTT这种保证通信可靠性的协议。
因此在子设备通用框架设计完成前,仍然使用Pando早期的二进制数据结构,其中flags等字段未与mqtt_bin_header的统一长度,预计下个版本会进行统一。- 二进制数据结构头部
struct device_header { uint8_t magic; /* 开始标志 (0x34) */ uint8_t crc; /* 校验和 */ uint16_t payload_type; /* 载荷类型 */ uint16_t payload_len; /* 载荷长度 */ uint16_t flags; /* 标志位,作用与mqtt header中的flag一致 ,长度会在下一版协议中更新 */ uint32_t frame_seq; /* 帧序列 */ };
- 子设备编号(subdevice_id)的控制
一个终端会有多个子设备,且具有动态扩展更多子设备的能力。对于那些动态加入的子设备,是无法知道自身的id的。子设备也没有必要知道自身的id
终端知道子设备的变化,包括动态添加的子设备。因此,子设备id由终端来管控,当终端收到子设备上报的数据包后,终端会打上子设备id,再转换成mqtt数据包,发往服务器
- payload部分
服务器、接入设备、硬件层设备,这三个层次间通信的payload没有区别,使用了统一的格式。
目的是为了减少接入设备的开销,因为接入设备作用是为了转发硬件层的数据,而不牵涉具体业务逻辑。 接入设备只需要重新组建header部分,就可将数据进行转发。
struct pando_command { uint16_t sub_device_id /* 子设备ID */ uint16_t command_num /* 命令编号 */ uint16_t priority /* 优先级 */ TLVs params[1] /* 参数,必须有一个参数区,其count可以为0 */ }; struct pando_event { uint16_t sub_device_id /* 子设备ID */ uint16_t event_num /* 事件编号 */ uint16_t priority /* 优先级 */ TLVs params[1] /* 参数,必须有一个参数区,其count可以为0 */ }; struct pando_data { struct pando_property properties[1]; /* 属性数组,至少有一个属性 */ };
其中属性pando_property 的定义为:
struct pando_property { uint16_t sub_device_id /* 子设备ID */ uint16_t property_num /* 属性编号 */ TLVs params[1] /* 参数,必须有一个参数区,其count可以为0 */ };
- TLVs与tlv的定义
tlv是type、length、value的缩写,对一个信息参数进行描述。
TLVs是一个或多个tlv的集合,其成员count表示包含了多少个tlv。 TLVs
struct TLVs { uint16_t count; /*表示紧接着有多少个tlv元素*/ tlv tlv_params[0] /*tlv 信息区*/ };
tlv只有当信息类型为bytes,uri等类型时,需要length字段,结构如下
struct tlv { uint16_t type uint16_t length uint8_t value[length] };
如果类型为int,double等固定长度的类型时,tlv结构就简化为
struct tlv
{
uint16_t type
uint8_t value[length]
}
类型及其枚举如下,终端与服务器要保持一致
#define TLV_TYPE_FLOAT64 1
#define TLV_TYPE_FLOAT32 2
#define TLV_TYPE_INT8 3
#define TLV_TYPE_INT16 4
#define TLV_TYPE_INT32 5
#define TLV_TYPE_INT64 6
#define TLV_TYPE_UINT8 7
#define TLV_TYPE_UINT16 8
#define TLV_TYPE_UINT32 9
#define TLV_TYPE_UINT64 10
#define TLV_TYPE_BYTES 11
#define TLV_TYPE_URI 12
#define TLV_TYPE_BOOL 13
```
MQTT协议为例,介绍从接入流程开始,如何完成连接和数据的交互。
本节以两个数据包为例,演示数据包各字段的含义,尤其是tlv与TLVs的概念。