设备 MQTT 接入协议
设备 MQTT 接入协议
概述
本文主要描述了如何注册和管理云平台设备 (包括边缘结点管理).
前端设备主要通过消息订阅/发布模式以和云端消息队列进行通信
本文定义的接口主要供前端设备调用.
系统前缀
API:
mqtt(s)://{domain-name}/
版本
V2.0
作者
@author ChengZhen
修改历史
2019/2/13
- 重写 API
2018/10/9
- 修改注册消息内容, 添加事物描述等信息
- 删除设备状态 API
- 修改边缘节点管理 API 名称
- 添加新的签名算法
MQTT API
服务器地址:
mqtt://{domain-name}:1883
管理后台地址:
http://{domain-name}:18083
NAME | VALUE | NOTE |
---|---|---|
Broker URL | {domain-name} | 消息服务器地址 |
Broker port | 1883 | 端口 |
Broker port with SSL | 8883 | 端口 |
username | Device ID | 设备的 ID |
password | Device Token | 设备访问令牌 |
Publish Path | messages/<did> |
设备发布指定的消息到云端 |
Subscription Path | actions/<did> |
设备订阅发送给它的 action 数据 |
Error Path | errors/<did> |
设备发布指定设备的错误信息到云端 |
Properties Path | things/<owner> /<pkey> /<did> /properties |
客户端可订阅的指定设备的属性数据流 |
Events Path | things/<owner> /<pkey> /<did> /events |
客户端可订阅的指定设备的事件流 |
Actions Path | things/<owner> /<pkey> /<did> /actions |
客户端可发布给指定设备的操作 |
Actions Result Path | things/<owner> /<pkey> /<did> /result |
客户端可订阅的指定设备的操作结果 |
Client ID | Device ID | 应用程序 ID |
设备 ID:
- 应用程序 ID (Client ID) 可以使用设备的 MAC 地址
MQTT 协议概述
发布订阅通信模型概述
通过发布订阅通信模型既可以使用 MQ 服务, 保证系统的高并发能力, 同时也能达到更复杂和更实时的通信能力.
消息队列 (MQ) 概述
通过在前端设备(网关)和云服务平台中间加入消息队列, 可以实现百万级设备并发能力.
- MQ 须支持标准 MQTT 协议.
- MQ 须支持 HTTP 协议接口用来发布数据
# 示意图
前端设备->消息队列: 建立 MQTT 连接
服务程序1->消息队列: 建立 MQTT 连接
服务程序1->消息队列: 订阅(/主题)
服务程序2->消息队列: 建立 MQTT 连接
服务程序2->消息队列: 订阅(/主题)
前端设备->消息队列: 发布(/主题, 数据)
消息队列-->服务程序1: 发布(/主题, 数据)
消息队列-->服务程序2: 发布(/主题, 数据)
如图所示, 所有订阅了同一主题的服务程序(订阅者)都能收到前端设备(发布者)发送到这个主题的数据
客户端 ID
{DeviceType}-{DeviceID}
- 设备的类型,如
gateway
- 设备的编号或 MAC 地址, 如
aa00123456cc
- 例如:
gateway-aa00123456cc
消息主题概述
在发布订阅通信模型和消息队列中, 主题 (Topic) 是非常重要的概念. 要最终实现完美的通信主题的名称和路径需要精心设计.
注意主题(Topic) 格式类似 Unix 文件路径, 但是 MQTT 主题名第一字符不需要 /
.
上行主题
messages/{DeviceID}
下行主题
actions/{DeviceID}
设备注册
设备通过发送注册消息来认证设备并获取访问令牌,具体流程如下。
设备->消息队列: 订阅 actions/:did
设备管理服务器->消息队列: 订阅 messages/:did
设备->消息队列: 发布注册请求 messages/:did
消息队列->设备管理服务器: 注册请求
设备管理服务器->消息队列: 发布注册结果 actions/:did
消息队列->设备: 注册结果
发布注册请求
发布主题:
PUBLISH messages/:did
注册请求消息:
{
"did": String,
"type": "register",
"timestamp": Number,
"sign": String,
"data": {
"expires": Number,
"version": {
"firmware": String
},
"supported": []
}
}
更新注册消息:
{
"did": String,
"type": "register",
"timestamp": Number,
"sign": String
}
删除注册消息:
{
"did": String,
"type": "register",
"timestamp": Number,
"sign": String,
"data": {
"expires": -1
}
}
请求参数:
NAME | REQUIRED | TYPE | DEFAULT | DESCRIPTION |
---|---|---|---|---|
did | true | String | - | 设备唯一 ID |
sign | - | String | - | 消息签名 |
timestamp | - | Number | - | 时间戳 |
type | true | String | register | 消息类型,必须是 register |
data | - | Object | - | 事物描述信息, 详情请参考 WoT 事物描述 |
- expires | - | Number | 3600 | 设备端期望的注册期满时间, 单位为秒 |
- nodes | - | Array | - | 可选,绑定到当前节点的边缘节点列表,常用于网关 |
- gateway | - | String | - | 可选,网关的 DID, 常用于边缘节点通过网关注册时 |
更多详细信息请考参相关的 HTTP API 的描述
接收注册结果
设备需订阅 actions/:did
主题接收应答结果
订阅主题:
SUBSCRIBE actions/:did
成功结果:
{
"did": String,
"type": "register",
"result": {
"id": String,
"token": String,
"expires": Number
}
}
NAME | REQUIRED | TYPE | DEFAULT | DESCRIPTION |
---|---|---|---|---|
did | true | String | - | 注册的设备的唯一 ID |
type | true | String | register | 消息类型,必须是 register |
token | true | Number | - | 令牌,用于后续消息加密和认证 |
expires | - | Number | 3600 | 云端期望的注册期满时间, 单位为秒 |
id | - | Number | - | 设备在云端的 ID |
错误结果:
{
"did": String,
"type": "register",
"error": {
"code": Number,
"error": String,
"message": String
}
}
CODE | TYPE | NOTE |
---|---|---|
100401 | Unauthorized | 设备没有通过身份认证 |
100403 | Permission denied | 该设备需要取得授权 |
104001 | Miss required parameter | 缺失必要的参数,请参考 API 文档 |
200202 | Device does not exists | 设备不存在 |
设备属性
上报数据流
前端设备通过发送数据流消息来主动上报数据流。
上报数据流也会同时更新设备影子的状态
采集服务器->消息队列: 订阅 messages/:did
设备->消息队列: 发布数据流 messages/:did
消息队列->采集服务器: 数据流
发布主题:
PUBLISH messages/:did
did
表示设备 ID. 即每一个设备对应一个消息主题.
消息内容:
{
"did": String,
"type": "stream",
"token": String,
"data": {
"temperature": 50
}
}
注意一个消息的大小最好不要超过 4KB.
接收读取操作
应用程序通过这个消息来主动查询前端设备的属性
设备->消息队列: 订阅 actions/:did
应用程序->消息队列: 订阅 messages/:did
应用程序->消息队列: 发布读属性请求 actions/:did
消息队列->设备: 读属性请求
设备->消息队列: 操作结果 messages/:did
消息队列->应用程序: 操作结果(属性值)
请求消息:
{
"did": String,
"mid": String,
"type": "action",
"data": {
"read": ["<name>"]
}
}
应答消息:
{
"did": String,
"mid": String,
"type": "action",
"result": {
"read": {
"<name>": <value>
}
}
}
接收修改操作
应用程序通过这个消息来主动修改前端设备的属性
设备->消息队列: 订阅 actions/:did
应用程序->消息队列: 订阅 messages/:did
应用程序->消息队列: 发布修改属性请求 actions/:did
消息队列->设备: 修改属性请求
设备->消息队列: 操作结果
消息队列->应用程序: 操作结果
请求消息:
{
"did": String,
"mid": String,
"type": "action",
"data": {
"write": {
"<name>": <value>
}
}
}
应答消息:
{
"did": String,
"mid": String,
"type": "action",
"result": {
"write": {
"code": 0
}
}
}
设备操作
应用程序通过发送这个消息来远程调用前端设备的操作方法。
设备->消息队列: 订阅 actions/:did
应用程序->消息队列: 订阅 messages/:did
应用程序->消息队列: 发布操作请求 actions/:did
消息队列->设备: 操作请求
设备->消息队列: 发布操作结果 messages/:did
消息队列->应用程序: 操作结果
接收操作请求
设备可以订阅如下的主题来接收发给它的操作 (Action).
订阅主题:
SUBSCRIBE actions/:did
did
表示设备 ID. 即每一个设备对应一个消息主题.
消息内容:
{
"did": String,
"mid": String,
"type": "action",
"data": {
"<name>": <input>
}
}
发送操作结果
发布主题:
PUBLISH messages/:did
did
表示设备 ID. 即每一个设备对应一个消息主题.
消息内容
{
"did": String,
"mid": String,
"type": "action",
"result": {
"<name>": <output>
}
}
注意一个消息的大小最好不要超过 4KB.
设备事件
前端设备通过事件消息主动上报事件信息
设备管理服务器->消息队列: 订阅 messages/:did
设备->消息队列: 发布事件 messages/:did
消息队列->设备管理服务器: 事件信息
发布事件
发布主题:
PUBLISH messages/:did
did
表示设备 ID. 即每一个设备对应一个消息主题.
消息内容:
{
"did": String,
"type": "event",
"token": String,
"data": {
"<name>": <value>
}
}
注意一个消息的大小最好不要超过 4KB.
设备影子 API
查询设备影子
设备可以通过这个操作查询设备影子的状态
发布请求消息
发布主题:
PUBLISH messages/:did
did
表示设备 ID. 即每一个设备对应一个消息主题.
消息内容:
{
"did": String,
"mid": String,
"type": "action",
"data": {
"shadow": {
"read": {}
}
}
}
接收应答消息
订阅主题:
SUBSCRIBE actions/:did
消息内容:
{
"did": String,
"mid": String,
"type": "action",
"result": {
"shadow": {
"read": {
"version": String,
"updated": Number,
"config": {
"version": String,
"updated": Number,
"<name>": <value>
},
"reported": {
"<name>": <value>
},
"desired": {
"<name>": <value>
},
"metadata": {
"reported": {
"updated": Number,
"<name>": {
"updated": Number
}
},
"desired": {
"updated": Number,
"<name>": {
"updated": Number
}
}
}
}
}
}
}
应答参数:
NAME | REQUIRED | TYPE | DESCRIPTION |
---|---|---|---|
did | true | String | 设备的 ID |
version | true | String | 设备影子的版本 |
updated | true | Number | 设备影子的最后更新时间 |
config | - | Object | 设备配置参数 |
reported | - | Object | 设备最后上报的属性或状态 |
desired | - | Object | 设备期望被配置的属性 |
metadata | - | Object | 设备影子元数据 |
更新设备影子
应用程序可以通过这个接口修改期望的属性,设备端可以通过这个接口上报实际的属性
发布请求消息
发布主题:
PUBLISH /messages/:did
请求内容:
{
"did": String,
"mid": String,
"type": "action",
"data": {
"shadow": {
"write": {
"reported": {
"<name>": <value>
},
"desired": {
"<name>": <value>
}
}
}
}
}
NAME | REQUIRED | TYPE | DEFAULT | DESCRIPTION |
---|---|---|---|---|
did | true | String | - | 设备 ID |
reported | - | Object | - | 上报设备当前属性或状态 |
desired | - | Object | - | 设置期望的设备属性 |
接收应答消息
订阅主题:
SUBSCRIBE actions/:did
响应结果:
{
"did": String,
"mid": String,
"type": "action",
"result": {
"shadow": {
"write": {
"code": 0
}
}
}
}
设备管理 API
具体协议请参考设备管理协议
一章, 下面是部分协议介绍:
订阅主题:
SUBSCRIBE actions/:did
固件升级
应用程序通如下面的流程来主动升级前端设备的固件。
在开始升级前应用程序需先上传要升级的固件方法到设备管理服务器。
设备->消息队列: 订阅操作消息
设备管理服务器->消息队列: 发布升级操作请求
消息队列->设备: 升级操作请求
设备->消息队列: 请求结果
消息队列->设备管理服务器: 请求结果
设备->设备管理服务器: 下载固件文件 (HTTP)
设备管理服务器->设备: 固件文件
设备->设备: 升级
设备->消息队列: 重新注册
消息队列->设备管理服务器: 重新注册
消息内容:
{
"did": String,
"mid": String,
"type": "action",
"data": {
"firmware": {
"update": {
"uri": String,
"size": Number,
"md5sum": String,
"version": String
}
}
}
}
应答内容
发布主题:
PUBLISH messages/:did
消息内容:
{
"did": String,
"mid": String,
"type": "action",
"result": {
"firmware": {
"update": {
"code": 0
}
}
}
}
参数配置
应用程序可以通过下面的流程修改前端设备的属性
config 是设备管理保留属性名,用来读写设备配置参数
设备->消息队列: 订阅
设备管理服务器->消息队列: 发布修改配置参数请求
消息队列->设备: 修改配置参数
设备->设备: 应用新的参数
设备->消息队列: 请求结果
消息队列->设备管理服务器: 请求结果
请求消息:
{
"did": String,
"mid": String,
"type": "action",
"data": {
"config": {
"write": {
"<name>": <value>,
}
}
}
}
应答内容
发布主题:
PUBLISH messages/:did
消息内容:
{
"did": String,
"mid": String,
"timestamp": Number,
"type": "action",
"result": {
"config": {
"write": {
"code": 0
}
}
}
}