转自IOT物联网技术
在物联网场景中,由于网络不稳定,导致设备间歇性离线状态;电池容量限制,很多 IoT 设备无法做到24小时在线,设备沉睡处于离线状态;这些现状带来一个新的挑战:在设备离线时,云端如何发送控制指令给设备?才能保证设备上线后,按照新的指令执行业务逻辑?
技 术 解 决 方 案
面对设备离线场景消息触达诉求,我们有两种通用解决方案:
- 基于 MQTT 协议 QoS=1 消息
- 基于 IoT 物联网平台的设备影子功能
方案一、发送QoS=1离线消息
MQTT 协议设计了一套保证消息稳定传输的机制,包括消息应答、存储和重传。在这套机制下,提供了三种不同层次服务质量QoS(Quality of Service):
- QoS=0,至多一次;
- QoS=1,至少一次;
- QoS=2,只有一次。
QoS=1 代表,Sender 发送的一条消息,Receiver 至少能收到一次,也就是说 Sender 向 Receiver 发送消息,如果发送失败,会继续重试,直到 Receiver 收到消息为止,但是因为重传的原因,Receiver 有可能会收到重复的消息;

当我们采用QoS=1方式发布消息到 IoT 物联网平台,即可保证消息至少到达设备端一次,再结合去重逻辑,重连时保留Session信息来实现离线消息触达。
1.1 设备端配置
设备建立 MQTT 连接时需要配置 CONNECT 参数 CleanSession=0,即保留之前建立的 session 状态,这包括:
- 客户端的订阅信息;
- 未完成确认的QoS=1的消息;
- 未发送给客户端的QoS=1的消息。
Node.js实现CONNECT参数示例:
const options = {clientId: `$ {id} | securemode = 3,signmethod = hmacsha1,timestamp = $ {timestamp} | `,username: `$ {deviceName} & $ {productKey}`,password: "根据文档规则进行 hmacsha1 加密",host: `$ {productKey}.iot - as - mqtt.cn - shanghai.aliyuncs.com`,protocol: "mqtt",clean: false,//重连后保持Sessionkeepalive: 300}
1.2 服务端调用
服务端调用 IoT 物联网平台的Pub API ,并指定Qos =1 。完整API 文档参考
https://help.aliyun.com/document_detail/69793.html
Node.js调用Pub API示例:
//1.构建 Pub API 请求报文const params = {TopicFullName: "下行指令的完整 Topic",MessageContent: "消息体的 base64 编码",ProductKey: "产品 ProductKey",IotInstanceId: "实例化 Id",Qos: 1 // 设备离线时,IoT平台缓存 7 天};//2.发起 Pub API 调用const response = yield client.request('Pub', params);
方案二、设备影子实现离线控制
IoT 物联网平台提供设备影子功能,可以实现离线设备的消息触达,完整消息链路如下如:
2.1 设备端开发
为了实现设备影子功能,IoT 设备端需要做两件事情:
- 订阅设备影子更新的Topic:/shadow/get/${productKey}/${deviceName} 以便实时获取云端控制指令消息;
- 设备CONNECT成功后,主动发布Topic和Payload 查询设备影子,用于获取云端最新影子数据。

Node.js 示例代码如下:
const mqtt = require('aliyun-iot-mqtt');//设备身份三元组+区域const deviceConfig = {"productKey": "产品","deviceName": "设备","deviceSecret": "设备deviceSecret","regionId": "cn-shanghai"};//1.建立连接const client = mqtt.getAliyunIotMqttClient(deviceConfig);//2.订阅设备影子topicconst getShadow = ` / shadow / get / $ {deviceConfig.productKey}/${deviceConfig.deviceName}`;client.subscribe(getShadow)client.on('message', function(topic, message) {/ / 收到消息后,显示设备影子中的远程配置参数if (topic == getShadow) {message = JSON.parse(message);console.log(new Date().Format("yyyy-MM-dd HH:mm:ss.S")) console.log("\tappConfig.content :", JSON.stringify(message.payload.state.desired.appConfig)) console.log("\tappConfig.timestamp :", JSON.stringify(message.payload.metadata.desired.appConfig.timestamp))}})//3.主动获取设备影子中的远程配置参数const updateShadow = ` / shadow / update / $ {deviceConfig.productKey}/${deviceConfig.deviceName}`;client.publish(updateShadow, JSON.stringify({method: "get"}), { qos: 1 })/
2.2 服务端调用
服务端调用设备影子接口 UpdateDeviceShadow 把新的配置参数保存到设备影子的desired中,接口文档:
https://help.aliyun.com/document_detail/69954.html
Node.js 示例代码如下:
const co = require('co');const RPCClient = require('@alicloud/pop-core').RPCClient;const options = {accessKey: "你的accessKey",accessKeySecret: "你的accessKeySecret",};//1.初始化clientconst client = new RPCClient({accessKeyId: options.accessKey,secretAccessKey: options.accessKeySecret,endpoint: 'https://iot.cn-shanghai.aliyuncs.com',apiVersion: '2018-01-20'});//2.desired中appConfig变更const shadowMessage = {method: "update",state: {desired: {appConfig: {maxTemperature: 39.5,}}},version: Date.now()}const params = {ProductKey: "你的ProductKey",DeviceName: "你的DeviceName",ShadowMessage: JSON.stringify(shadowMessage)};co(function * () {try {//3.发起API调用,更新影子中配置参数const response = yield client.request('UpdateDeviceShadow', params);console.log(JSON.stringify(response));} catch(err) {console.log(err);}});
2.3 设备影子运行
云端业务系统调用成功后,我们在 IoT 物联网平台的控制台,设备详情>设备影子,查看设备影子信息。具体如下:
在线设备,实时获取更新
设备在线时,设备通过订阅设备影子的Topic实时获得云端配置参数。
离线设备,上线后获取更新
设备离线时,设备影子缓存云端配置参数,设备上线后,主动从云端拉取最新的配置参数。这时配置参数更新的时间会比当前时间早,设备端可以根据这个时间来判断是否要使用新的配置参数。

