转自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 有可能会收到重复的消息;

IoT 设备离线时,云端下行消息触达方案 - 图2
当我们采用QoS=1方式发布消息到 IoT 物联网平台,即可保证消息至少到达设备端一次,再结合去重逻辑,重连时保留Session信息来实现离线消息触达。

1.1 设备端配置

设备建立 MQTT 连接时需要配置 CONNECT 参数 CleanSession=0,即保留之前建立的 session 状态,这包括:

  • 客户端的订阅信息;
  • 未完成确认的QoS=1的消息;
  • 未发送给客户端的QoS=1的消息。

Node.js实现CONNECT参数示例:

  1. const options = {
  2. clientId: `$ {
  3. id
  4. } | securemode = 3,
  5. signmethod = hmacsha1,
  6. timestamp = $ {
  7. timestamp
  8. } | `,
  9. username: `$ {
  10. deviceName
  11. } & $ {
  12. productKey
  13. }`,
  14. password: "根据文档规则进行 hmacsha1 加密",
  15. host: `$ {
  16. productKey
  17. }.iot - as - mqtt.cn - shanghai.aliyuncs.com`,
  18. protocol: "mqtt",
  19. clean: false,
  20. //重连后保持Session
  21. keepalive: 300
  22. }

1.2 服务端调用

服务端调用 IoT 物联网平台的Pub API ,并指定Qos =1 。完整API 文档参考
https://help.aliyun.com/document_detail/69793.html
IoT 设备离线时,云端下行消息触达方案 - 图3
Node.js调用Pub API示例:

  1. //1.构建 Pub API 请求报文
  2. const params = {
  3. TopicFullName: "下行指令的完整 Topic",
  4. MessageContent: "消息体的 base64 编码",
  5. ProductKey: "产品 ProductKey",
  6. IotInstanceId: "实例化 Id",
  7. Qos: 1 // 设备离线时,IoT平台缓存 7 天
  8. };
  9. //2.发起 Pub API 调用
  10. const response = yield client.request('Pub', params);

方案二、设备影子实现离线控制

IoT 物联网平台提供设备影子功能,可以实现离线设备的消息触达,完整消息链路如下如:
IoT 设备离线时,云端下行消息触达方案 - 图4

2.1 设备端开发

为了实现设备影子功能,IoT 设备端需要做两件事情:

  • 订阅设备影子更新的Topic:/shadow/get/${productKey}/${deviceName} 以便实时获取云端控制指令消息;
  • 设备CONNECT成功后,主动发布Topic和Payload 查询设备影子,用于获取云端最新影子数据。

IoT 设备离线时,云端下行消息触达方案 - 图5

Node.js 示例代码如下:

  1. const mqtt = require('aliyun-iot-mqtt');
  2. //设备身份三元组+区域
  3. const deviceConfig = {
  4. "productKey": "产品",
  5. "deviceName": "设备",
  6. "deviceSecret": "设备deviceSecret",
  7. "regionId": "cn-shanghai"
  8. };
  9. //1.建立连接
  10. const client = mqtt.getAliyunIotMqttClient(deviceConfig);
  11. //2.订阅设备影子topic
  12. const getShadow = ` / shadow / get / $ {
  13. deviceConfig.productKey
  14. }
  15. /${deviceConfig.deviceName}`;
  16. client.subscribe(getShadow)
  17. client.on('message', function(topic, message) {
  18. / / 收到消息后,显示设备影子中的远程配置参数
  19. if (topic == getShadow) {
  20. message = JSON.parse(message);
  21. 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))
  22. }
  23. })
  24. //3.主动获取设备影子中的远程配置参数
  25. const updateShadow = ` / shadow / update / $ {
  26. deviceConfig.productKey
  27. }
  28. /${deviceConfig.deviceName}`;
  29. client.publish(updateShadow, JSON.stringify({method: "get"}), { qos: 1 })/

2.2 服务端调用

服务端调用设备影子接口 UpdateDeviceShadow 把新的配置参数保存到设备影子的desired中,接口文档:
https://help.aliyun.com/document_detail/69954.html
IoT 设备离线时,云端下行消息触达方案 - 图6

Node.js 示例代码如下:

  1. const co = require('co');
  2. const RPCClient = require('@alicloud/pop-core').RPCClient;
  3. const options = {
  4. accessKey: "你的accessKey",
  5. accessKeySecret: "你的accessKeySecret",
  6. };
  7. //1.初始化client
  8. const client = new RPCClient({
  9. accessKeyId: options.accessKey,
  10. secretAccessKey: options.accessKeySecret,
  11. endpoint: 'https://iot.cn-shanghai.aliyuncs.com',
  12. apiVersion: '2018-01-20'
  13. });
  14. //2.desired中appConfig变更
  15. const shadowMessage = {
  16. method: "update",
  17. state: {
  18. desired: {
  19. appConfig: {
  20. maxTemperature: 39.5,
  21. }
  22. }
  23. },
  24. version: Date.now()
  25. }
  26. const params = {
  27. ProductKey: "你的ProductKey",
  28. DeviceName: "你的DeviceName",
  29. ShadowMessage: JSON.stringify(shadowMessage)
  30. };
  31. co(function * () {
  32. try {
  33. //3.发起API调用,更新影子中配置参数
  34. const response = yield client.request('UpdateDeviceShadow', params);
  35. console.log(JSON.stringify(response));
  36. } catch(err) {
  37. console.log(err);
  38. }
  39. });

**

2.3 设备影子运行

云端业务系统调用成功后,我们在 IoT 物联网平台的控制台,设备详情>设备影子,查看设备影子信息。具体如下:
IoT 设备离线时,云端下行消息触达方案 - 图7

在线设备,实时获取更新

设备在线时,设备通过订阅设备影子的Topic实时获得云端配置参数。
IoT 设备离线时,云端下行消息触达方案 - 图8

离线设备,上线后获取更新

设备离线时,设备影子缓存云端配置参数,设备上线后,主动从云端拉取最新的配置参数。这时配置参数更新的时间会比当前时间早,设备端可以根据这个时间来判断是否要使用新的配置参数。

IoT 设备离线时,云端下行消息触达方案 - 图9