使用设备管理启动设备固件更新 (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 中心的名称。 如果输入的名称有效,则显示一个绿色复选标记。

    Important

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

    • 定价和缩放级别:对于本教程,请选择F1 - 免费级别。 有关详细信息,请参阅定价和缩放层

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

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

    • 固定仪表板:选中此选项可以方便地从仪表板访问 IoT 中心。

      IoT 中心窗口

  4. 单击“创建”。 创建 IoT 中心可能需要数分钟的时间。 可在“通知”窗格中监视进度。

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

    新建 IoT 中心窗口

  6. 在“共享访问策略”中,单击“iothubowner”策略,然后记下“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 解决方案以及在多个设备上计划方法调用,请参阅计划和广播作业教程。