v1.0.0
2023 年 2 月
1 简介
三三物联网设备接入协议是基于 MQTT 设计实现的,HTTP、CoAP、Modbus、用户 自定义等接入协议可以通过软网关的方式自由扩展。
平台提供了一些物联网中常用的标准协议软网关,并且在不断的增加中。
本文档详细描述了三三物联网的标准接入协议:SSIOT-MQTT。
2 物模型
在详细介绍三三物联网协议之前,需要了解三三物联网的核心设计思想,三三物模型是整个设计的核心概念,他将物进行了抽象与统一,物不仅仅只能是系统中的终端,也可以是各种进程,比如接入设备的软网关是一个进程也是一个物。物与物是完全对等的,可以进行对等的通讯与操作。
物,最初的什么都没有,然后诞生了一些属性,有了这些属性我们才能感知这个物,属性越来越多所以庸俗的被分为了四类:属性信息、变量模型、控制功能、配置参数。属性信息表示的物的基本信息,包含如SN(物的唯一标识),系统信息、运行环境等等。变量模型是物的业务体现,概念来源与传统工业组态软件中变量,比如温度计的温度变量,智能空开设备的开关变量。控制功能是物对外提供的能力,可供外部调用,比如重启、读取日志等。配置参数是物的参数信息,比如缓存大小,日志基本等等。可以看出来,这是开发者对物的属性分类,没错,三三物联网是面向开发者的平台。更加正确的是平台本身也是一个物:$。
物的模型在三三平台中用Profile来表达,其格式为JSON ,并且采用了标准的JSON Schema来定义Profile,当然在一些元数据类型上,我们在Schema的基础上做了一些扩展,比如视频类型,URL类型等等。大部分情况下开发者并不需要关心Schema,他被淹没在了优秀的架构设计中:)。
{
"profile": "device",
"description": "设备描述文件",
"sensor": {
"temperature": 24.0
},
"props": {
"basic": {
"sn": "13900000000"
}
},
"config": {
"sensor": {
"temperature": {
"unit": "℃"
}
}
},
"func": {}
}
以上就是一个简单的Profile,sensor表示了变量模型,props表示属性信息,config表示了配置参数,func表示了控制功能。
3 协议框架
三三物联的协议定义了与物的通讯,用物的SN 表示了通讯地址。协议的核心是对物Profile中的四类属性(config、props、sensor、func)进行的操作。主要流程如下:
登录、登出:主要是为了鉴权,分配服务地址等;
上报:物将自己的各项能力对外发布,接收者无需应答,对外发布的目的是让外部可操作;
get/set:物提供自己的各项属性的读写方法,并给与对方应答;
功能调用:执行功能调用的方法,给与应答;
并不是所有的物都需要完成上述的流程,例如一个温度计传感器,它的作用就是提供采集到的环境温度信息,不接受外部的任何操作,所以它只需实现一个sensor上报的协议,其他的协议都不需要实现包含登录。
但是对于一个Modubs采集设备(网关),一开始就需要接受使用者对他的各种参数配置,根据配置信息才能对外上报其下位机的各种变量数据,所以它需要实现的协议就多一些,登录、上报、get、set都可能要实现。总之 协议是按需实现的,协议的框架只做最简单的定义 。
3.1 主题定义
因采用了mqtt为承载协议,所以协议的主题框架为:
/iot/{目的地址}/{源地址}/{Profile}/{操作}/<扩展>
协议主题列表如下:
- 登录登出
协议 | 主题 |
---|---|
登录 | /iot/{目的地址}/{源地址}/login |
登录回应 | /iot/{目的地址}/{源地址}/login/ack |
登出 | /iot/{目的地址}/{源地址}/logout |
- 属性
协议 | 主题 |
---|---|
属性上报 | /iot/{目的地址}/{源地址}/props |
获取属性 | /iot/{目的地址}/{源地址}/props/get |
获取属性应答 | /iot/{目的地址}/{源地址}/props/get/ack |
设置属性 | /iot/{目的地址}/{源地址}/props/set |
设置属性应答 | /iot/{目的地址}/{源地址}/props/set/ack |
- 变量
协议 | 主题 |
---|---|
变量上报 | /iot/{目的地址}/{源地址}/sensor |
获取变量 | /iot/{目的地址}/{源地址}/sensor/get |
获取变量应答 | /iot/{目的地址}/{源地址}/sensor/get/ack |
设置变量 | /iot/{目的地址}/{源地址}/sensor/set |
设置变量应答 | /iot/{目的地址}/{源地址}/sensor/set/ack |
- 配置
协议 | 主题 |
---|---|
配置上报 | /iot/{目的地址}/{源地址}/config |
获取配置 | /iot/{目的地址}/{源地址}/config/get |
获取配置应答 | /iot/{目的地址}/{源地址}/config/get/ack |
设置配置 | / iot/{目的地址}/{源地址}/config/set |
设置配置应答 | / iot/{目的地址}/{源地址}/config/set/ack |
- 功能
协议 | 主题 |
---|---|
功能上报 | / iot/{目的地址}/{源地址}/func |
功能调用 | / iot/{目的地址}/{ 源 地址 }/func/{name} |
功能调用应答 | /iot/{目的地址}/{源地址}/func/{name}/ack |
3.2 消息体定义
消息体采用JSON格式,第一层级都是系统消息头,数据都包含在data对象中,具体 格式如下:
{
"sid": 1,
"to" : "$",
"from" : "13900000000",
"code": 200,
"msg": "welcome",
"data": {}
}
参数 | 描述 | 是否必填 |
---|---|---|
sid | 会话ID,发起方填写,应答方在回复中原样返回,可不填 | 否 |
to | 发送的目的地址,网关路由使用,由网关处理,可不填 | 否 |
from | 发送的源地址,网关路由使用,由网关处理,可不填 | 否 |
code | 应答的结果码,应答消息必填 | 是 |
msg | 应答的信息,可不填 | 否 |
3.3 路由机制
一般情况都不需要关注路由,只有在一些网关开发的时候需要用到路由机制。在主题中我们定义了消息投递的目的地址与源地址,对于两个能够直接通讯的设备这很好理解,对于不能直接通讯的设备,需要通过网关进行转发,这个时候就要用到路由机制了。
上图中,Y与$能直接通讯,Y向$请求配置,发送/iot/$/Y/confg/get,$接受Y的请求,处理后回复给Y,发送/iot/Y/$/confg/get/ack,一个业务完成。
X与$不能直接通讯,需要通过网关G转发,这种情况,通讯多了一个路由:
1 、X->G:发送请求:/iot/$/X/config/get;
2 、G->$:发送请求:/iot/$/G/config/get,消息体中需要将原地址X填充:From=X;
3 、$->G:回复应答:/iot/G/$/config/get/ack,消息体中需要将目的地址X填充:To=X;
4 、G->X:回复应答:/iot/X/$/config/get/ack;
以上机制也可以实现多级路由。
如果网关带的下位机并不多,建议这里可以将下位机直接定义为网关的某个属性,实现 网关与下位机的逻辑组合,形成一个新的物,这样就不需要路由了。
3.4 状态码
状态码 | 英文 | 中文 | 说明 |
---|---|---|---|
2** | 成功 | ||
200 | OK | 成功 | |
4** | 客户端发生的错误 | ||
400 | Request Error | 请求错误 | 比如:参数错误 |
401 | Unauthorized | 要求用户的身份认证 | |
404 | Not Found | 资源不存在 | 比如:网关无此下位机、无此数据资源 |
5** | 服务器发生的错误 | ||
500 | Server Error | 服务器错误 | 服务器端发生的错误,比如服务为实现 |
501 | Not Implemented | 不支持请求的功能 |
3.5 始于:$
$:代表了一个物地址,三三物联网中的第一个物,三三平台的地址。
4 协议详情
三三平台实现了对接入平台物(终端、设备、网关...)的管理等功能,以下协议描述了具体协议内容。$表示平台地址,{sn}表示设备地址。三三平台实现了以下协议:
- 登录登出
协议 | 主题 |
---|---|
登录 | /iot/{目的地址}/{源地址}/login |
登录回应 | /iot/{目的地址}/{源地址}/login/ack |
登出 | /iot/{目的地址}/{源地址}/logout |
- 属性
协议 | 主题 |
---|---|
属性上报 | /iot/{目的地址}/{源地址}/props |
获取属性 | /iot/{目的地址}/{源地址}/props/get |
获取属性应答 | /iot/{目的地址}/{源地址}/props/get/ack |
设置属性 | /iot/{目的地址}/{源地址}/props/set |
设置属性应答 | /iot/{目的地址}/{源地址}/props/set/ack |
- 变量
协议 | 主题 |
---|---|
变量上报 | /iot/{目的地址}/{源地址}/sensor |
获取变量 | /iot/{目的地址}/{源地址}/sensor/get |
获取变量应答 | /iot/{目的地址}/{源地址}/sensor/get/ack |
设置变量 | /iot/{目的地址}/{源地址}/sensor/set |
设置变量应答 | /iot/{目的地址}/{源地址}/sensor/set/ack |
- 配置
协议 | 主题 |
---|---|
配置上报 | /iot/{目的地址}/{源地址}/config |
获取配置 | /iot/{目的地址}/{源地址}/config/get |
获取配置应答 | /iot/{目的地址}/{源地址}/config/get/ack |
设置配置 | / iot/{目的地址}/{源地址}/config/set |
设置配置应答 | / iot/{目的地址}/{源地址}/config/set/ack |
- 功能
协议 | 主题 |
---|---|
功能上报 | / iot/{目的地址}/{源地址}/func |
功能调用 | / iot/{目的地址}/{ 源 地址 }/func/{name} |
功能调用应答 | /iot/{目的地址}/{源地址}/func/{name}/ack |
以下是SS10A设备的Profile,辅助下列章节协议的详细说明。
{
"profile": "SS10A",
"description": "SS10A设备描述文件",
"sensor": {
"temperature": 24.0,
"gps" : {
"longitude" : 114.31158155,
"latitude" : 30.59846674
}
},
"props": {
"basic": {
"sn": "13900000000",
"model": "SS10A"
},
"system": {
"host": {
"kernelArch": "x86_64",
"kernelVersion": "10.0.19045 Build 19045",
"os": "windows"
}
}
},
"config": {
"sensor": {
"temperature": {
"unit": "℃"
}
},
"ssiot": {
"log": {
"format": "",
"level": "debug"
}
}
},
"func": {
"reboot":{
"input":null,
"output":{
"code":200
}
}
}
}
4.1 登录登出
4.1.1 设备登录与登录应答
/iot/$/{sn}/login
{
"sid": 1,
"data" : {
"model" : "SS10A"
}
}
字段 | 意义 | 是否必填 | 备注 |
---|---|---|---|
sid | 会话ID | 否 | |
model | 产品型号 | 否 | 平台使用该字段识别产品类型 |
/iot/{sn}/$/login/ack
{
"sid": 1,
"to" : "{sn}",
"from" : "$",
"code": 200,
"msg": "welcome",
"data": {
"goto": "192.168.10.1:9100"
}
}
字段 | 意义 | 是否必填 | 备注 |
---|---|---|---|
sid | 会话ID | 否 | 后续章节不再描述该字段 |
to | 目的地址 | 否 | 后续章节不再描述该字段 |
from | 源地址 | 否 | 后续章节不再描述该字段 |
code | 状态码 | 是 | 参考状态码定义,后续章节不再描述该字段 |
msg | 消息 | 否 | 后续章节不再描述该字段 |
goto | 重定向地址 | 否 | 收到该地址后,终端需要重定向到指定的服务器,重新登录。 |
4.2.2 平台获取设备属性与应答
/iot/{sn}/$/props/get
{
"sid": 2,
"data": {
"path": "system"
}
}
字段 | 意义 | 是否必填 | 备注 |
---|---|---|---|
path | 获取属性的JSON路径 | 否 | 根据该路径,平台返回对应的属性数据JSON。路径支持多级,以下类同。 |
/iot/$/{sn}/props/get/ack
{
"sid": 2,
"code": 200,
"msg": "success",
"data": {
"system": {
"host": {
"kernelArch": "x86_64",
"kernelVersion": "10.0.19045 Build 19045",
"os": "windows"
}
}
}
}
字段 | 意义 | 是否必填 | 备注 |
---|---|---|---|
data | 数据 | 否 | 平台根据请求返回的数据,以上一个具体的例子 |
返回的数据中以层级的方式展现,可以合并更新,以下类同 |
4.3 配置参数
4.3.1 终端配置上报
/iot/$/{sn}/config
{
"data": {
"ssiot": {
"log": {
"format": "",
"level": "debug"
}
}
}
}
4.3.2 终端获取平台配置与应答
/iot/$/{sn}/config/get
{
"sid": 3,
"data": {
"path": "ssiot.log"
}
}
/iot/{sn}/$/config/get/ack
{
"sid": 3,
"code": 200,
"msg": "success",
"data": {
"ssiot": {
"log": {
"format": "",
"level": "info"
}
}
}
}
4.3.3 平台设置终端配置与应答
/iot/{sn}/$/config/set
{
"sid": 4,
"data": {
"sensor": {
"temperature": {
"unit": "℃"
}
}
}
}
/iot/$/{sn}/config/set/ack
{
"sid": 4,
"code": 200,
"msg": "success"
}
4.3.4 配置删除
配置的删除采用:/iot/{sn}/$/config/set主题,与设置配置一样。当设置的配置对象为null的时候表示删除该配置,比如:“sensor”:null,可以删除整个配置,也可以删除某个具体配置项。
4.4 变量模型
4.4.1 终端变量上报
/iot/$/{sn}/sensor
{
"data" : {
"temperature": 24.0,
"gps" : {
"longitude" : 114.31158155,
"latitude" : 30.59846674
}
}
}
4.4.2 平台获取变量与应答
/iot/{sn}/$/sensor/get
{
"sid": 6,
"data": {
"path": "temperature"
}
}
/iot/$/{sn}/sensor/get/ack
{
"sid": 6,
"code": 200,
"msg": "success",
"data": {
"temperature": 24.0
}
}
4.4.3 平台设置变量与应答
/iot/{sn}/$/sensor/set
{
"sid": 7,
"data": {
"gps" : {
"longitude" : 114.31158155,
"latitude" : 30.59846674
}
}
}
/iot/$/{sn}/sensor/set/ack
{
"sid": 7,
"code": 200,
"msg": "success"
}
4.5 功能控制
4.5.1 终端功能上报
/iot/$/{sn}/func
{
"data" : {
"reboot":{
"input":null,
"output":{
"code":200
}
}
}
}
4.5.2 平台调用终端功能与返回
/iot/{sn}/$/func/reboot
{
"sid" : 8,
"data" : {
"input" : null
}
}
/iot/$/{sn}/func/reboot/ack
{
"sid": 8,
"code": 200,
"msg": "success",
"data": {
"output": {
"code": 200
}
}
}
5 子设备(subdevice)
子设备是指通过网关与外部进行通讯的设备。比如一个温度传感器,用485串口连接到一个 4G采集网关(SS10B),由网关采集温度并上报数据到平台。这种情况,温度传感器是一个子设备,他的温度数据只能通过网关对外进行通讯。
5.1 网关subdevice配置
子设备与网关都是设备,都采用Profile进行描述,他们都有各自的config、sensor、props、func,一般情况下他们没有特殊的地方,但是有一种情况是需要考虑的。
因为某些子设备是被动设备,他的数据采集与上报是由网关主动获取,所以网关定义了被动设备(子设备)的数据采集配置信息,然后子设备与数据采集相关配置信息是根据他所属网关配置定义来生成的。总结如下:
网关:定义子设备配置的模板;
子设备:根据所属网关的配置模板,实现具体配置;
我们约定,网关定义子设备的配置模板在Profile的config下的subdevice节点下,可参考下面SS10B Profile与温度计 Profile的例子。
SS10B Profile:
{
"profile": "SS10B",
"description": "SS10B设备描述文件",
"sensor": {
},
"props": {
},
"config": {
"ssiot": {
"log": {}
},
"subdevice": {
"ch": "COM1",
"proto": "modbusRTU",
"addr": "设备地址",
"sensor": {
"unit": "℃",
"reg": "寄存器地址",
"func": "功能码"
}
}
},
"func": {}
}
温度计 Profile:
{
"profile": "device",
"description": "温度计",
"sensor": {
"temperature": 24.0
},
"props": {
},
"config": {
"ch": "COM1",
"proto": "modbusRTU",
"addr": "1",
"sensor": {
"temperature": {
"unit": "℃",
"reg": "0",
"func": "3"
}
}
},
"func": {}
}
5.2 子设备与平台的通讯
因为子设备不能直接与平台进行通讯,所以平台向子设备发送消息的时候需要经过网关路由。当子设备向平台发送信息的时候:主题中的源地址为网关地址,消息体的真实源地址为子设备地址。当平台向子设备发送信息的时候:
主题中的设备地址为网关地址,消息体的真实设备地址为子设备地址。具体可参考路由机制。
其他的协议与设备直接通讯协议完全一致,以下以子设备配置获取与设置举例;
5.2.1 平台设置子设备配置与应答
平台配置:
/iot/{gateway-sn}/$/config/set
{
"sid": 1866,
"to": "subdevice-sn",
"data": {
}
}
子设备应答:
/iot/$/{gateway-sn}/config/set/ack
{
"from": "subdevice-sn",
"sid": 1866,
"code": 200,
"msg": "success"
}
5.2.2 子设备获取平台配置与应答
子设备请求:
/iot/$/{gateway-sn}/config/get
{
"sid": 3,
"from": "subdevice-sn",
"data": {
"path": "ssiot.log"
}
}
平台应答:
/iot/{gateway-sn}/$/config/get/ack
{
"sid": 3,
"to": "subdevice-sn",
"code": 200,
"msg": "success",
"data": {
"ssiot": {
"log": {
"format": "",
"level": "info"
}
}
}
}
5.3 网关向平台获取所有子设备配置
网关在重置后可能会丢失子设备配置信息,需要向平台获取所有的子设备配置信息。值得注意的是,如果一个网关设计的时候预计会带有大量子设备,那么该网关需要慎重向平台发送获取全部子设备的消息,可能会产生大量的数据交互,数据量过大平台可能会回复失败,这个时候可以在平台上逐个向网关发送设备配置命令(参考章节:4.3.3)。
网关发送:
/iot/$/{gateway-sn}/config/get/subdevice
{
"sid": 3,
"data": {
}
}
平台回应获取子设备请求:
/iot/{gateway-sn}/$/config/get/subdevice/ack
{
"sid": 3,
"code": 200,
"msg": "success",
"data": {
"subdevice-sn-1": {
},
"subdevice-sn-2": {
},
"subdevice-sn-3": {
},
"subdevice-sn-4": {
},
"subdevice-sn-N": {
},
}
}
5.4 删除子设备
删除子设备复用配置删除的消息,平台删除了子设备,需要发送配置删除命令,具体如下:
/iot/{gateway-sn}/$/config/set
{
"sid": 4,
"to": "subdevice-sn",
"data": null
}
/iot/$/{gateway-sn}/config/set/ack
{
"sid": 4,
"from": "subdevice-sn",
"code": 200,
"msg": "success"
}
6 FAQ
6.1 操作的下位机不存在如何回复
比如平台向网关设置下位机,网关判断下位机不存在,应该由网关回复错误,回复的主题也是网关的SN,并且采用正确的状态码:404
6.2 什么情况要主动上报配置、功能等
该协议并没有规定所有的协议都要实现,开发者可以按需实现,比如一个温度计,不接受配置等功能,甚至登录都不需要(安全的问题由其他的策略保障),只需实现变量上报协议,并只有当温度变化的时候才上报,这样让终端的开发变得最简单。
/iot/$/{sn}/sensor
{
"data" : {
"temperature": 24.0
}
}
当然,对于一个需要来自UI端的配置驱动数据采集的网关终端,最好是实现一个终端主动的配置信息上报动作(一般在登录的时候上报),主动上报的这个配置信息就是一个默认的配置,对用来讲,只需修改他所需要的配置项就行,这样的实现方式是把复杂度留给了终端的开发者,让用户尽量简单,一个好的产品应该是这样的。