节点与云端通信
本文概述了节点如何通过基于 TLS 的 MQTT 与云端安全交互。每个节点在 Claiming 过程中都会建立唯一的标识符和凭证,以确保安全的认证通信。节点配置信息包含设备和参数等属性,设备启动时会将其发送到云端,供客户端(如移动应用)解析和使用。理解这一通信框架有助于开发人员有效集成和管理 ESP RainMaker 生态系统中的物联网设备。
节点与云端之间的所有通信均通过安全的 MQTT 进行。
节点如何确保与云端的安全通信?
节点通过以下方式确保与云端的安全通信:
- 传输层安全 (TLS)
- MQTT 策略
TLS
节点与云端之间的通信基于 X.509 证书的双向认证 TLS 加密进行保护,确保仅可信设备可建立连接。
建立安全通信的一般流程:
- 节点生成自己的私钥,并创建证书签名请求 (CSR),类似于申请官方身份证。
- 该请求被发送到认证机构 (CA),CA 验证后签名并返 回客户端证书,相当于认证身份证。
- 在 Claiming 过程中,节点接收此证书,从而与云端建立安全加密连接。
- 在进行任何数据交换之前,节点和云端都会验证彼此的证书,确保与正确的实体通信。
- 这样可以确保隐私和安全,防止未经授权的访问,阻止伪造或恶意设备侵入系统。
- 确保只有可信节点可以连接到云端。
- 防止黑客或伪造设备冒充真实节点。
- 保证所有通信的隐私和安全。
MQTT 策略
MQTT 策略确保节点只有在客户端 ID 与节点 ID 匹配时才能连接到云端,防止未经授权的访问。此外,节点仅限于发布和订阅以 node/<node_id>/ 开头的主题,而无法访问其他节点的主题。
例如,如果节点的 ID 为 1234,它只能与 node/1234/status 或 node/1234/command 等主题交互,但不能订阅 node/5678/status。
该策略作为一种安全机制,可以保持每个节点的通信互相隔离且受保护,确保数据的私密性,防止节点之间互相干扰。
总结
| 安全措施 | 描述 |
|---|---|
| TLS | 锁定通信通道,防止窃听或篡改消息。 |
| MQTT 策略 | 定义节点在安全通道内允许通信的内容。 |
MQTT 主题
什么是 MQTT 主题?
在 ESP RainMaker 中,MQTT 主题是节点进行通信的路径,用于发布(发送)状态更新和订阅(接收)命令或配置信息。以下是 ESP RainMaker 中的各类 MQTT 主题。
节点配置
在 ESP RainMaker 中,节点配置是关于节点(如 ESP32-S3)的完整信息集合。节点启动时会将这些配置信息发送到 ESP RainMaker 云,并同步给客户端(如移动应用程序和命令行界面 CLI)。此配置包含用于定义节点身份、功能和性能的所有必要信息。
下文按层级顺序介绍了 ESP RainMaker 固件和手机应用程序当前使用的节点配置布局。
节点配置布局(展开/收起)
- 红色框中代表节点配置中的必填字段。
- 绿色框中代表字段的必填参数。
- 字段下方的字符串表示对应的键 (JSON 格式,类型为数据)。
1. 节点 ID
2. 配置版本
2. 信息
3. 节点属性
4. 设备和参数
5. 服务和参数
MQTT
- 主题:
node/<node_id>/config - 操作:
PUBLISH - 数据:
节点配置 JSON 对象,见下方示例
JSON 有效载荷(展开/收起)
{
"node_id": "9gaogaZz7jGA7J6yAhe6Kd",
"config_version": "2020-03-20",
"info": {
"name": "My_Bridge",
"model": "ESP32-Bridge-V1",
"fw_version": "1.0",
"type": "Bridge",
"project_name": "esp_rainmaker_bridge",
"platform": "esp32"
},
"attributes": [{
"name": "serial_num",
"value": "123abc"
}, {
"name": "model",
"value": "myBridge-2019"
}, {
"name": "cmd-resp",
"value": "1"
}],
"devices": [{
"name": "Switch",
"type": "esp.device.switch",
"primary": "power",
"params": [{
"name": "name",
"type": "esp.param.name",
"data_type": "string",
"properties": ["read", "write"]
}, {
"name": "power",
"type": "esp.param.power",
"data_type": "bool",
"properties": ["read", "write", "time_series"],
"ui_type": "esp.ui.toggle"
}]
}, {
"name": "Light",
"type": "esp.device.lightbulb",
"attributes": [{
"name": "serial_number",
"value": "012345"
}, {
"name": "mac",
"value": "xx:yy:zz:aa:bb:cc"
}],
"primary": "power",
"params": [{
"name": "name",
"type": "esp.param.name",
"data_type": "string",
"properties": ["read", "write"]
}, {
"name": "power",
"type": "esp.param.power",
"data_type": "bool",
"properties": ["read", "write", "time_series"],
"ui_type": "esp.ui.toggle"
}, {
"name": "brightness",
"type": "esp.param.brightness",
"data_type": "int",
"properties": ["read", "write"],
"bounds": {
"min": 0,
"max": 100
},
"ui_type": "esp.ui.slider"
}, {
"name": "Hue",
"type": "esp.param.hue",
"data_type": "int",
"properties": ["read", "write"],
"bounds": {
"min": 0,
"max": 360,
"step": 1
},
"ui_type": "esp.ui.hue-slider"
}, {
"name": "Saturation",
"type": "esp.param.saturation",
"data_type": "int",
"properties": ["read", "write"],
"bounds": {
"min": 0,
"max": 100,
"step": 1
},
"ui_type": "esp.ui.slider"
}]
}],
"services": [{
"name": "OTA",
"type": "esp.service.ota",
"params": [{
"name": "Status",
"type": "esp.param.ota_status",
"data_type": "string",
"properties": ["read"]
}, {
"name": "Info",
"type": "esp.param.ota_info",
"data_type": "string",
"properties": ["read"]
}, {
"name": "URL",
"type": "esp.param.ota_url",
"data_type": "string",
"properties": ["write"]
}]
}, {
"name": "Time",
"type": "esp.service.time",
"params": [{
"name": "TZ",
"type": "esp.param.tz",
"data_type": "string",
"properties": ["read", "write"]
}, {
"name": "TZ-POSIX",
"type": "esp.param.tz_posix",
"data_type": "string",
"properties": ["read", "write"]
}]
}, {
"name": "Schedule",
"type": "esp.service.schedule",
"params": [{
"name": "Schedules",
"type": "esp.param.schedules",
"data_type": "array",
"properties": ["read", "write"],
"bounds": {
"max": 10
}
}]
}, {
"name": "Local Control",
"type": "esp.service.local_control",
"params": [{
"name": "POP",
"type": "esp.param.local_control_pop",
"data_type": "string",
"properties": ["read"]
}, {
"name": "Type",
"type": "esp.param.local_control_type",
"data_type": "int",
"properties": ["read"]
}]
}, {
"name": "Scenes",
"type": "esp.service.scenes",
"attributes": [{
"name": "deactivation_support",
"value": "no"
}],
"params": [{
"name": "Scenes",
"type": "esp.param.scenes",
"data_type": "array",
"properties": ["read", "write"],
"bounds": {
"max": 10
}
}]
}, {
"name": "System",
"type": "esp.service.system",
"params": [{
"name": "Reboot",
"type": "esp.param.reboot",
"data_type": "bool",
"properties": ["write"]
}, {
"name": "Factory-Reset",
"type": "esp.param.factory_reset",
"data_type": "bool",
"properties": ["write"]
}, {
"name": "Wi-Fi-Reset",
"type": "esp.param.wifi_reset",
"data_type": "bool",
"properties": ["write"]
}]
}]
}
默认情况下,设备在上电并连接到云端后会报告一次节点信息,也可以通过调用 esp_rmaker_report_node_details 进行报告。
固件 API: esp_rmaker_report_node_details()
节点参数
所有参数更改通过特定的节点参数主题进行管理。
初始上报
每次节点启动时都会进行初始上报,包含所有设备的所有参数值。
MQTT
- 主 题:
node/<node_id>/params/local/init - 操作:
PUBLISH - 数据:
{"<device-name">:{"<param-name>":<value>,...},...}
JSON 有效载荷(展开/收起)
{
"Lightbulb": {
"name": "Bedroom Light",
"power": true,
"brightness": 55
}
}
节点首次启动并连接到云端时将报告所有参数的当前值,或通过调用 esp_rmaker_report_node_state API 来报告所有参数值。
固件 API: esp_rmaker_report_node_state()
后续上报
节点上的任何后续更改,无论是远程控制还是本地更改,都将通过此主题报告。有效载荷中只需要包含已更改的参数,用于向云端报告参数值。
MQTT
- 主题:
node/<node_id>/params/local - 操作:
PUBLISH - 数据:
{"<device-name">:{"<param-name>":<value>,...},...}
JSON 有效载荷(展开/收起)
{
"Lightbulb": {
"brightness": 75
}
}
当设备参数发生变化并需要向云端报告时,调用 esp_rmaker_param_update_and_report API。
固件 API: esp_rmaker_param_update_and_report()
远程控制
节点必须始终订阅此主题,监听由客户端(如手机应用或 CLI)触发的更新。每次更新后产生的参数值更改都必须进行后续上报。
MQTT
- 主题:
node/<node_id>/params/remote - 操作:
SUBSCRIBE - 数据:
{"<device-name">:{"<param-name>":<value>,...},...}
JSON 有效载荷(展开/收起)
{
"Lightbulb": {
"brightness": 100
}
}
节点启动并连接到云端后,将自动订阅此主题。
用户-节点映射
在对节点下的设备进行远程监控和控制之前,需先将节点映射到特定用户,仅允许该用户访问该节点。这一过程发生在 Wi-Fi 配置阶段的后台。节点将其 node_id 发送给客户端,客户端将 user_id 和 secret_key 发送给节点进行映射。一旦节点连接到 RainMaker 云,将发送映射请求。请参考用户节点映射了解详细流程。
MQTT
- 主题:
node/<node_id>/user/mapping - 操作:
PUBLISH - 数据:
{"node_id":"<node_id>","user_id":"<user_id>","secret_key": "<secret_key>"}
JSON 有效载荷(展开/收起)
{
"node_id": "112233AABBCC",
"user_id": "02e95749-8d9d-4b8e-972c-43325ad27c63",
"secret_key": "9140ef1d-72be-48d5-a6a1-455a27d77dee"
}
设备绑定或解绑网络时触发,也可以通过调用 esp_rmaker_start_user_node_mapping API 手动触发,或者调用 esp_rmaker_user_mapping_endpoint_register() API,使用户-节点映射在 Wi-Fi 配置期间自动发生。
固件 API: esp_rmaker_start_user_node_mapping() 或 esp_rmaker_user_mapping_endpoint_register()
时间序列
ESP RainMaker 提供两种时间序列服务,一种是全功能时间序列,另一种是简单时间序列。两种服务之间的区别,请参阅时间序列数据。
时间序列数据
时间序列数据适用于需要完整时间戳和历史值的场景,例如查找最小值、最大值、平均值等。全功能时间序列支持在单条 MQTT 消息中同时上报多个参数和多条记录,适用于历史记录功能。相比简单时间序列,完整时间序列利用了 AWS 的内嵌功能,支持数据统计与计算,但相应成本更高。
MQTT
- 主题:
node/<node_id>/tsdata - 操作:
PUBLISH
数据模板(展开/收起)
{
"ts_data_version": "2021-09-13",
"ts_data": [
{
"name": "<param_name>",
"dt": "<int/float/bool/string>",
"records": [
{
"t": <epoch_timestamp>,
"v": <value>
},
{
more records...
}
]
},
{
more parameters...
}
]
}
JSON 有效载荷(展开/收起)
{
"ts_data_version": "2021-09-13",
"ts_data": [
{
"name": "Temperature Sensor.Temperature",
"dt": "float",
"records": [
{
"t": 1699468430,
"v": 26.5
}
]
}
]
}
当设备属性设置为 PROP_FLAG_TIME_SERIES 并调用 esp_rmaker_param_update_and_report() API 时,将报告该参数的时间序列数据。
简单时间序列数据
此功能支持在 MQTT 消息中仅报告单个参数的单个数据点,用于历史记录功能。与全功能时间序列数据相比,简单时间序列数据仅存储原始数据,不支持云端统计和计算,成本较低。
MQTT
- 主题:
node/<node_id>/simple_tsdata - 操作:
PUBLISH
数 据模板(展开/收起)
{
"name": "<param_name>",
"dt": "<int/float/bool/string>",
"t": <epoch_timestamp>,
"v": <value>
}
JSON 有效载荷(展开/收起)
{
"name": "Temperature Sensor.Temperature",
"dt": "float",
"t": 1704189730,
"v": 25.5
}
当设备属性设置为 PROP_FLAG_SIMPLE_TIME_SERIES,并调用 esp_rmaker_param_update_and_report() 时,将报告该参数的简单时间序列数据。
注意,示例中的参数名称是设备名称和参数名称的组合,目的是为了让手机应用程序关联到节点配置中的正确参数。但就参数属性而言,时间序列数据独立于其他 RainMaker 概念(如设备、参数等),如果需要,可以单独使用。
警报和通知
此功能更新参数值并通知云端和连接的客户端,或触发特定事件或条件的警报。发布后,移动应用程序将显示通知弹窗 。
MQTT
- 主题:
node/<node_id>/alert - 操作:
PUBLISH
当节点需要发送警报或通知时,调用 esp_rmaker_param_update_and_notify 或 esp_rmaker_raise_alert。
固件 API: esp_rmaker_param_update_and_notify() 或 esp_rmaker_raise_alert()
命令响应
命令响应可用于节点离线的场景。云端可通 过应用程序配置设备,当节点上线时,相关配置将被推送至节点。命令响应可在云端进行添加和修改,可通过 Swagger API 中的命令响应请求进行操作,配置将在节点上电后推送到节点。
到节点
MQTT
- 主题:
node/<node_id>/to-node - 操作:
SUBSCRIBE
启用命令响应功能后,设备将上电并建立云端连接,并自动订阅此主题。
从节点
用于响应来自云端的命令。
MQTT
- 主题:
node/<node_id>/from-node - 操作:
PUBLISH
接收到平台的命令后,节点将处理该命令并通过上述主题内部响应。
OTA
OTA 使用 MQTT 主题来管理 OTA 流程,包括报告 OTA 状态、获取 OTA 任务以及监听 OTA 更新。
报告 OTA 任务状态
在 OTA 过程中报告状态。
MQTT
- 主题:
node/<node_id>/otastatus - 操作:
PUBLISH
在 OTA 升级过程中,设备将报告当前 OTA 状态。用户也可以调用 esp_rmaker_ota_report_status API 来报告状态。
固件 API: esp_rmaker_ota_report_status()
获取 OTA 任务
设备查询 OTA 任务。在平台强制升级或用户确认升级后,如果设备离线,可以获取 OTA 任务并在上线时执行升级。
MQTT
- 主题:
node/<node_id>/otafetch - 操作:
PUBLISH
设备上电并连接到云端后将主动获取 OTA 任务,默认每24 小时获取一次。也可以通过调用 esp_rmaker_ota_fetch 或 esp_rmaker_ota_fetch_with_delay API 手动触发。
固件 API: esp_rmaker_ota_fetch() 或 esp_rmaker_ota_fetch_with_delay()
监听 OTA 任务
当管理面板上创建了强制 OTA 或用户批准的 OTA,并且用户通过应用程序触发升级时,云端将通过此主题推送 OTA URL、固件版本、固件大小等信息。
MQTT
- 主题:
node/<node_id>/otaurl - 操作:
SUBSCRIBE
设备连接到云端后,将自动订阅 otaurl 主题以监听云端发布的 OTA 任务。