使用设备管理启动设备固件更新 (Node/Node)

设备管理入门教程中,已了解如何使用设备孪生直接方法基元来远程重新启动设备。 本教程使用相同的 IoT 中心基元,提供指南,并演示如何进行端到端模拟固件更新。 此模式在用于 Intel Edison 设备示例的固件更新实现中使用。

Note

本文中所述的功能仅可在 IoT 中心的标准层中使用。 有关基本和标准 IoT 中心层的详细信息,请参阅如何选择合适的 IoT 中心层

本教程演示如何:

  • 创建一个 Node.js 控制台应用,该应用通过 IoT 中心在模拟设备应用上调用 firmwareUpdate 直接方法。
  • 创建模拟设备应用,以便实现 firmwareUpdate 直接方法。该方法会启动等待下载固件映像、下载固件映像以及最后应用固件映像的多阶段过程。在更新的每个阶段,设备都使用报告的属性来报告进度。

本教程结束时,会创建两个 Node.js 控制台应用:

dmpatterns_fwupdate_service.js,它调用模拟设备应用中的直接方法、显示响应并定期(每隔 500 毫秒)显示更新的报告属性。

dmpatterns_fwupdate_device.js,它使用早前创建的设备标识连接到 IoT 中心、接收 firmwareUpdate 直接方法、运行一个多状态过程以模拟固件更新,包括:等待映像下载、下载新映像以及最后应用映像。

要完成本教程,需要以下各项:

  • Node.js 版本 4.0.x 或更高版本;
  • 准备开发环境介绍了如何在 Windows 或 Linux 上安装本教程所用的 Node.js。
  • 有效的 Azure 帐户。(如果没有帐户,只需花费几分钟就能创建一个试用帐户。)

按照设备管理入门一文创建 IoT 中心,并获取 IoT 中心连接字符串。

创建 IoT 中心

创建模拟设备应用要连接到的 IoT 中心。 以下步骤说明如何使用 Azure 门户来完成此任务。

  1. 登录到 Azure 门户

  2. 选择“创建资源” > “物联网” > “IoT 中心”。

    Azure 门户跳转栏

  3. 在“IoT 中心”窗格中,输入 IoT 中心的以下信息:

    • 订阅:选择需要将其用于创建此 IoT 中心的订阅。

    • 资源组:创建用于托管 IoT 中心的资源组,或使用现有的资源组。 有关详细信息,请参阅使用资源组管理 Azure 资源

    • 区域:选择最近的位置。

    • 名称:创建 IoT 中心的名称。 如果输入的名称可用,会显示一个绿色复选标记。

    Important

    IoT 中心将公开为 DNS 终结点,因此,命名时请务必避免包含任何敏感信息。

    IoT 中心基本信息窗口

  4. 选择“下一步: 大小和规模”,以便继续创建 IoT 中心。

  5. 选择“定价和缩放层”。 就本文来说,请选择“F1 - 免费”层(前提是此层在订阅上仍然可用)。 有关详细信息,请参阅定价和缩放层

    IoT 中心大小和规模窗口

  6. 选择“查看 + 创建”。

  7. 查看 IoT 中心信息,然后单击“创建”。 创建 IoT 中心可能需要数分钟的时间。 可在“通知”窗格中监视进度。

  8. 新的 IoT 中心就绪以后,请在 Azure 门户中单击其磁贴,打开其属性窗口。 创建 IoT 中心以后,即可找到将设备和应用程序连接到 IoT 中心时需要使用的重要信息。 单击“共享访问策略”。

  9. 在“共享访问策略”中,选择 iothubowner 策略。 复制 IoT 中心连接字符串 ---主密钥供以后使用。 有关详细信息,请参阅“IoT 中心开发人员指南”中的访问控制

    共享访问策略

创建设备标识

本部分使用名为 iothub-explorer 的 Node.js 工具为本教程创建设备标识。 设备 ID 区分大小写。

  1. 在命令行环境中运行以下命令:

    npm install -g iothub-explorer@latest

  2. 然后,运行以下命令登录到中心。 将 {iot hub connection string} 替换为前面复制的 IoT 中心连接字符串:

    iothub-explorer login "{iot hub connection string}"

  3. 最后,以下使用命令创建名为 myDeviceId 的新设备标识:

    iothub-explorer create myDeviceId --connection-string

    Important

    收集的日志中可能会显示设备 ID 用于客户支持和故障排除,因此,在为日志命名时,请务必避免包含任何敏感信息。

记下结果中的设备连接字符串。 设备应用使用此设备连接字符串以设备身份连接到 IoT 中心。

若要以编程方式创建设备标识,请参阅 IoT 中心入门

使用直接方法在设备上触发远程固件更新

在本部分,用户创建一个 Node.js 控制台应用,以便在设备上启动远程固件更新。 该应用使用直接方法来启动更新,并使用设备孪生查询定期获取活动固件更新的状态。

  1. 创建一个名为 triggerfwupdateondevice 的空文件夹。 在 triggerfwupdateondevice 文件夹的命令提示符处,使用以下命令创建 package.json 文件。 接受所有默认值:

    npm init
    
  2. 在 triggerfwupdateondevice 文件夹中,通过命令提示符运行以下命令,安装 azure-iot-hub 包:

    npm install azure-iothub --save
    
  3. 在 triggerfwupdateondevice 文件夹中,使用文本编辑器创建 dmpatterns_getstarted_service.js 文件。
  4. dmpatterns_getstarted_service.js 文件开头添加以下“require”语句:

    'use strict';
    
    var Registry = require('azure-iothub').Registry;
    var Client = require('azure-iothub').Client;
    
  5. 添加以下变量声明并替换占位符值:

    var connectionString = '{device_connectionstring}';
    var registry = Registry.fromConnectionString(connectionString);
    var client = Client.fromConnectionString(connectionString);
    var deviceToUpdate = 'myDeviceId';
    
  6. 添加以下函数以查找并显示 firmwareUpdate 报告属性的值。

    var queryTwinFWUpdateReported = function() {
        registry.getTwin(deviceToUpdate, function(err, twin){
            if (err) {
              console.error('Could not query twins: ' + err.constructor.name + ': ' + err.message);
            } else {
              console.log((JSON.stringify(twin.properties.reported.iothubDM.firmwareUpdate)) + "\n");
            }
        });
    };
    
  7. 添加以下函数以调用 firmwareUpdate 方法来重新启动目标设备:

    var startFirmwareUpdateDevice = function() {
      var params = {
          fwPackageUri: 'https://secureurl'
      };
    
      var methodName = "firmwareUpdate";
      var payloadData =  JSON.stringify(params);
    
      var methodParams = {
        methodName: methodName,
        payload: payloadData,
        timeoutInSeconds: 30
      };
    
      client.invokeDeviceMethod(deviceToUpdate, methodParams, function(err, result) {
        if (err) {
          console.error('Could not start the firmware update on the device: ' + err.message)
        } 
      });
    };
    
  8. 最后,向代码添加以下函数,以便启动固件更新序列并开始定期显示报告属性:

    startFirmwareUpdateDevice();
    setInterval(queryTwinFWUpdateReported, 500);
    
  9. 保存并关闭 dmpatterns_fwupdate_service.js 文件。

创建模拟设备应用程序

本部分的操作:

  • 创建一个 Node.js 控制台应用,用于响应通过云调用的直接方法
  • 触发模拟的固件更新
  • 使用报告属性,允许通过设备孪生查询标识设备及其上次完成固件更新的时间
  1. 创建名为 manageddevice的空文件夹。 在 manageddevice 文件夹的命令提示符处,使用以下命令创建 package.json 文件。 接受所有默认值:

    npm init
    
  2. manageddevice 文件夹的命令提示符处,运行下述命令以安装 azure-iot-deviceazure-iot-device-mqtt 设备 SDK 包:

    npm install azure-iot-device azure-iot-device-mqtt --save
    
  3. manageddevice 文件夹中,利用文本编辑器创建 dmpatterns_fwupdate_device.js 文件。

  4. dmpatterns_fwupdate_device.js 文件开头添加以下“require”语句:

    'use strict';
    
    var Client = require('azure-iot-device').Client;
    var Protocol = require('azure-iot-device-mqtt').Mqtt;
    
  5. 添加 connectionString 变量,并使用它创建一个客户端实例。 将 {yourdeviceconnectionstring} 占位符替换为此前在“创建设备标识”部分记下的连接字符串:

    var connectionString = '{yourdeviceconnectionstring}';
    var client = Client.fromConnectionString(connectionString, Protocol);
    
  6. 添加以下函数,用于更新报告的属性:

    var reportFWUpdateThroughTwin = function(twin, firmwareUpdateValue) {
      var patch = {
          iothubDM : {
            firmwareUpdate : firmwareUpdateValue
          }
      };
    
      twin.properties.reported.update(patch, function(err) {
        if (err) throw err;
        console.log('twin state reported: ' + firmwareUpdateValue.status);
      });
    };
    
  7. 添加用于模拟固件映像的下载和应用的以下函数:

    var simulateDownloadImage = function(imageUrl, callback) {
      var error = null;
      var image = "[fake image data]";
    
      console.log("Downloading image from " + imageUrl);
    
      callback(error, image);
    }
    
    var simulateApplyImage = function(imageData, callback) {
      var error = null;
    
      if (!imageData) {
        error = {message: 'Apply image failed because of missing image data.'};
      }
    
      callback(error);
    }
    
  8. 添加通过报告属性将固件更新状态更新为“正在等待”的以下函数。 通常,设备会收到有关可用更新的通知,并且管理员定义的策略会使设备开始下载和应用更新。 此函数是用于启用该策略的逻辑应该运行的位置。 为简单起见,该示例在开始下载固件映像之前会显示四秒:

    var waitToDownload = function(twin, fwPackageUriVal, callback) {
      var now = new Date();
    
      reportFWUpdateThroughTwin(twin, {
        fwPackageUri: fwPackageUriVal,
        status: 'waiting',
        error : null,
        startedWaitingTime : now.toISOString()
      });
      setTimeout(callback, 4000);
    };
    
  9. 添加通过报告属性将固件更新状态更新为“正在下载”的以下函数。 然后,该函数会模拟固件下载,并最终将固件更新状态更新为“downloadFailed”或“downloadComplete”:

    var downloadImage = function(twin, fwPackageUriVal, callback) {
      var now = new Date();   
    
      reportFWUpdateThroughTwin(twin, {
        status: 'downloading',
      });
    
      setTimeout(function() {
        // Simulate download
        simulateDownloadImage(fwPackageUriVal, function(err, image) {
    
          if (err)
          {
            reportFWUpdateThroughTwin(twin, {
              status: 'downloadfailed',
              error: {
                code: error_code,
                message: error_message,
              }
            });
          }
          else {        
            reportFWUpdateThroughTwin(twin, {
              status: 'downloadComplete',
              downloadCompleteTime: now.toISOString(),
            });
    
            setTimeout(function() { callback(image); }, 4000);   
          }
        });
    
      }, 4000);
    }
    
  10. 添加通过报告属性将固件更新状态更新为“正在应用”的以下函数。 然后,该函数会模拟固件映像应用,并最终将固件更新状态更新为“applyFailed”或“applyComplete”:

    var applyImage = function(twin, imageData, callback) {
      var now = new Date();   
    
      reportFWUpdateThroughTwin(twin, {
        status: 'applying',
        startedApplyingImage : now.toISOString()
      });
    
      setTimeout(function() {
    
        // Simulate apply firmware image
        simulateApplyImage(imageData, function(err) {
          if (err) {
            reportFWUpdateThroughTwin(twin, {
              status: 'applyFailed',
              error: {
                code: err.error_code,
                message: err.error_message,
              }
            });
          } else { 
            reportFWUpdateThroughTwin(twin, {
              status: 'applyComplete',
              lastFirmwareUpdate: now.toISOString()
            });    
    
          }
        });
    
        setTimeout(callback, 4000);
    
      }, 4000);
    }
    
  11. 添加处理 firmwareUpdate 直接方法并启动多阶段固件更新过程的以下函数:

    var onFirmwareUpdate = function(request, response) {
    
      // Respond the cloud app for the direct method
      response.send(200, 'FirmwareUpdate started', function(err) {
        if (!err) {
          console.error('An error occured when sending a method response:\n' + err.toString());
        } else {
          console.log('Response to method \'' + request.methodName + '\' sent successfully.');
        }
      });
    
      // Get the parameter from the body of the method request
      var fwPackageUri = request.payload.fwPackageUri;
    
      // Obtain the device twin
      client.getTwin(function(err, twin) {
        if (err) {
          console.error('Could not get device twin.');
        } else {
          console.log('Device twin acquired.');
    
          // Start the multi-stage firmware update
          waitToDownload(twin, fwPackageUri, function() {
            downloadImage(twin, fwPackageUri, function(imageData) {
              applyImage(twin, imageData, function() {});    
            });  
          });
    
        }
      });
    }
    
  12. 最后添加以下代码,以便连接到 IoT 中心:

    client.open(function(err) {
      if (err) {
        console.error('Could not connect to IotHub client');
      }  else {
        console.log('Client connected to IoT Hub.  Waiting for firmwareUpdate direct method.');
      }
    
      client.onDeviceMethod('firmwareUpdate', onFirmwareUpdate);
    });
    

Note

为简单起见,本教程不实现任何重试策略。 在生产代码中,应该按 MSDN 文章 Transient Fault Handling(暂时性故障处理)中所述实施重试策略(例如指数性的回退)。

运行应用

现在,已准备就绪,可以运行应用。

  1. manageddevice 文件夹的命令提示符处,运行以下命令进行直接重启方法的侦听。

    node dmpatterns_fwupdate_device.js
    
  2. triggerfwupdateondevice 文件夹的命令提示符处运行以下命令,以便触发远程重启并查询设备孪生了解上次重新启动时间。

    node dmpatterns_fwupdate_service.js
    
  3. 可在控制台查看对直接方法的设备响应。

后续步骤

在本教程中,直接触发了设备的远程固件更新,并使用了报告属性跟踪固件更新的进度。

若要了解如何扩展 IoT 解决方案以及在多个设备上计划方法调用,请参阅计划和广播作业教程。