在这一节我们开始来实现 OTA 升级的设备端功能。

上报升级进度

首先我们使用一个类来封装上报升级进度的操作:

  1. //IotHub_Device/sdk/ota_progress.js
  2. const ObjectId = require('bson').ObjectID;
  3. class OTAProgress {
  4. constructor({productName, deviceName, mqttClient, version, type}) {
  5. this.productName = productName
  6. this.deviceName = deviceName
  7. this.mqttClient = mqttClient
  8. this.version = version
  9. this.type = type
  10. }
  11. sendProgress(progress) {
  12. var meta = {
  13. version: this.version,
  14. type: this.type
  15. }
  16. var topic = `update_ota_status/${this.productName}/${this.deviceName}/${new ObjectId().toHexString()}`
  17. this.mqttClient.publish(topic, JSON.stringify({...meta, ...progress}), {qos: 1})
  18. }
  19. download(percent, desc = "download") {
  20. this.sendProgress({progress: percent, desc: desc})
  21. }
  22. downloadError(desc = "download error"){
  23. this.download(-1, desc)
  24. }
  25. checkMD5Error(desc = "check md5 error"){
  26. this.sendProgress({progress: -2, desc: desc})
  27. }
  28. installError(desc = "install error"){
  29. this.sendProgress({progress: -3, desc: desc})
  30. }
  31. error(desc = "error"){
  32. this.sendProgress({progress: -4, desc: desc})
  33. }
  34. }

这个类封装了上报升级进度的各个节点的操作,设备应用代码只需要在相应的节点调用对应的方法就是了。比如下载升级包时调用 progress.download(17),安装失败时调用 progress.installError()

响应OTA指令

在收到$ota_upgrade指令以后,DeviceSDK 代码需要把指令数据传递给设备应用代码:

  1. //IotHub_Device/sdk/iot_device.js
  2. handleCommand({commandName, requestID, encoding, payload, expiresAt, commandType = "cmd"}) {
  3. ...
  4. if (commandName.startsWith("$")) {
  5. payload = JSON.parse(data.toString())
  6. if (commandName == "$set_ntp") {
  7. this.handleNTP(payload)
  8. } else if (commandName == "$set_tags") {
  9. this.setTags(payload)
  10. }else if(commandName == "$ota_upgrade"){
  11. var progress = new OTAProgress({
  12. productName: this.productName,
  13. deviceName: this.deviceName,
  14. mqttClient: this.client,
  15. version: payload.version,
  16. type: payload.type
  17. })
  18. this.emit("ota_upgrade", payload, progress)
  19. }
  20. }
  21. ...
  22. }
  23. }

DeviceSDK 除了把 OTA 升级相关的数据通过”ota_upgrade”事件传递给设备应用代码,还传递了一个 OTAProgress 对象,设备应用代码可以调用这个对象方法正确上报升级进度。

代码联调

设备端的功能就全部实现了,接下来我们写一端代码来测试 OTA 升级功能。
首先实现一个设备端来模拟 OTA 升级,它收到 OTA 升级指令以后,会每隔 2 秒更新一次下载进度,当下载进度达到 100% 时,会再等待 3 秒之后上报更新后的软件版本,我们用这样的方式来模拟一次成功的 OTA 升级流程:

  1. //IotHub_Device/samples/ota_upgrade.js
  2. ...
  3. const currentVersion = "1.0"
  4. device.on("online", function () {
  5. console.log("device is online")
  6. })
  7. device.on("ota_upgrade", function (ota, progress) {
  8. console.log(going to upgrade ${ota.type}: ${ota.url}, version=${ota.version})
  9. var percent = 0
  10. var performUpgrade = function () {
  11. console.log(`download:${percent}`)
  12. progress.download(percent)
  13. if(percent < 100){
  14. percent += 20
  15. setTimeout(performUpgrade, 2000)
  16. }else{
  17. setTimeout(function () {
  18. device.updateStatus({firmware_ver: ota.version})
  19. }, 3000)
  20. }
  21. }
  22. performUpgrade()
  23. })
  24. device.connect()
  25. device.updateStatus({firmware_ver: currentVersion})

然后模拟业务系统,向设备发送 OTA 指令以后,每隔 1 秒钟查询一次设备的升级进度,进度达到 100 以后就检查设备的软件版本,如果设备的软件版本和预期一致,就说明设备已经升级成功了:

  1. //IotHub_Server/samples/perform_ota.js
  2. ...
  3. var otaData = {
  4. type: "firmware",
  5. url: "http://test.com/firmware/1.1.pkg",
  6. version: "1.1",
  7. size: 1000,
  8. md5: "abcd"
  9. }
  10. var progress = 0
  11. var checkUpgradeProgress = function () {
  12. if (progress < 100) {
  13. request.get(`http://127.0.0.1:3000/ota/${process.env.TARGET_PRODUCT_NAME}/${process.env.TARGET_DEVICE_NAME}`, function (err, res, body) {
  14. if (!err && res.statusCode == 200) {
  15. var info = JSON.parse(body);
  16. if(info.version == otaData.version) {
  17. progress = info.progress
  18. console.log(`current progress:${progress}%`);
  19. }
  20. setTimeout(checkUpgradeProgress, 1000)
  21. }
  22. })
  23. } else {
  24. request.get(`http://127.0.0.1:3000/devices/${process.env.TARGET_PRODUCT_NAME}/${process.env.TARGET_DEVICE_NAME}`, function (err, res, body) {
  25. if (!err && res.statusCode == 200) {
  26. var info = JSON.parse(body);
  27. console.log(`current version:${info.device_status.firmware_ver}`);
  28. if (info.device_status.firmware_ver == otaData.version) {
  29. console.log(`upgrade completed`);
  30. } else {
  31. setTimeout(checkUpgradeProgress, 1000)
  32. }
  33. }
  34. })
  35. }
  36. }
  37. console.log("perform upgrade")
  38. request.post(`http://127.0.0.1:3000/ota/${process.env.TARGET_PRODUCT_NAME}/${process.env.TARGET_DEVICE_NAME}`, {
  39. form: otaData
  40. }, function (error, response) {
  41. if (error) {
  42. console.log(error)
  43. } else {
  44. console.log('statusCode:', response && response.statusCode);
  45. checkUpgradeProgress()
  46. }
  47. })

先运行 IotHub\_Device/samples/ota\_upgrade.js,然后运行 IotHub\_Server/samples/perform_ota.js,观察下输出:

  1. ## IotHub_Device/samples/ota_upgrade.js
  2. device is online
  3. going to upgrade firmware: http://test.com/firmware/1.1.pkg, version=1.1
  4. download:0
  5. download:20
  6. download:40
  7. download:60
  8. download:80
  9. download:100
  10. upgrade completed
## IotHub_Server/samples/perform_ota.js
perform upgrade
statusCode: 200
current progress:0%
current progress:0%
current progress:20%
current progress:20%
current progress:40%
current progress:60%
current progress:60%
current progress:80%
current progress:80%
current progress:100%
current version:1.0
current version:1.0
current version:1.1
upgrade completed

这说明 OTA 升级功能在正确工作了。

这一节,我们完成了 IotHub 设备的 OTA 升级功能,在接下来的几节课中,我们会讨论如何设计和实现一个新的功能: 设备影子。