转自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,
//重连后保持Session
keepalive: 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.订阅设备影子topic
const 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.初始化client
const 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实时获得云端配置参数。
离线设备,上线后获取更新
设备离线时,设备影子缓存云端配置参数,设备上线后,主动从云端拉取最新的配置参数。这时配置参数更新的时间会比当前时间早,设备端可以根据这个时间来判断是否要使用新的配置参数。