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

介绍

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

本教程演示如何:

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

本教程结束时,用户会有一个 Node.js 控制台设备应用,以及一个 .NET (C#) 控制台后端应用:

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

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

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

  • Visual Studio 2015 或 Visual Studio 2017。
  • Node.js 版本 0.12.x 或更高版本,
    准备开发环境介绍了如何在 Windows 或 Linux 上安装本教程所用的 Node.js。
  • 有效的 Azure 帐户。 如果没有帐户,可以创建一个试用帐户,只需几分钟即可完成。

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

创建 IoT 中心

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

  1. 登录到 Azure 门户
  2. 在“跳转栏”中,依次单击“新建” > “物联网” > “IoT 中心”。

    Azure 门户跳转栏

  3. 在“IoT 中心”边栏选项卡中,选择 IoT 中心的配置。

    IoT 中心边栏选项卡

    • 在“名称”框中,输入 IoT 中心的名称。 如果该“名称”有效且可用,“名称”框中会出现绿色的勾选标记。
    • 选择 定价和缩放层。 本教程不需要特定的层。 对于本教程,请使用免费 F1 层。
    • 在“资源组”中,创建资源组或选择现有的资源组。 有关详细信息,请参阅使用资源组管理 Azure 资源
    • 在“位置”中,选择托管 IoT 中心的位置。 对于本教程,请选择最近位置。
  4. 选择 IoT 中心配置选项后,单击“创建”。 Azure 可能需要几分钟时间来创建 IoT 中心。 若要检查状态,可以在“启动板”或“通知”面板中监视进度。

    新的 IoT 中心状态

  5. 成功创建 IoT 中心后,请在 Azure 门户中单击 IoT 中心对应的新磁贴,以打开新 IoT 中心的边栏选项卡。 记下“主机名”,然后单击“共享访问策略”。

    新的 IoT 中心边栏选项卡

  6. 在“共享访问策略”边栏选项卡中,单击“iothubowner”策略,然后复制并记下“iothubowner”边栏选项卡中的 IoT 中心连接字符串。 有关详细信息,请参阅“IoT 中心开发人员指南”中的访问控制

    共享访问策略边栏选项卡

创建设备标识

在本部分中,使用名为 IoT 中心资源管理器 的 Node.js 工具为本教程创建设备标识。

  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
    

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

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

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

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

  1. 在 Visual Studio 中,使用“ 控制台应用程序 ”项目模板将 Visual C# Windows 经典桌面项目添加到当前解决方案。 将项目命名为 TriggerFWUpdate

    新的 Visual C# Windows 经典桌面项目

  2. 在“解决方案资源管理器”中,右键单击“TriggerFWUpdate”项目,然后单击“管理 NuGet 包”。

  3. 在“NuGet 包管理器”窗口中,选择“浏览”,搜索 microsoft.azure.devices,选择“安装”以安装 Microsoft.Azure.Devices 包,然后接受使用条款。 该过程将下载、安装 Azure IoT 服务 SDK NuGet 包及其依赖项并添加对它的引用。

    “NuGet 包管理器”窗口

  4. Program.cs 文件顶部添加以下 using 语句:

     using Microsoft.Azure.Devices;
     using Microsoft.Azure.Devices.Shared;
    
  5. 将以下字段添加到 Program 类。 将多个占位符值替换为在上一部分中为中心创建的 IoT 中心连接字符串和设备 ID。

     static RegistryManager registryManager;
     static string connString = "{iot hub connection string}";
     static ServiceClient client;
     static JobClient jobClient;
     static string targetDevice = "{deviceIdForTargetDevice}";
    
  6. 将以下方法添加到 Program 类:

     public static async Task QueryTwinFWUpdateReported()
     {
         Twin twin = await registryManager.GetTwinAsync(targetDevice);
         Console.WriteLine(twin.Properties.Reported.ToJson());
     }
    
  7. 将以下方法添加到 Program 类:

     public static async Task StartFirmwareUpdate()
     {
         client = ServiceClient.CreateFromConnectionString(connString);
         CloudToDeviceMethod method = new CloudToDeviceMethod("firmwareUpdate");
         method.ResponseTimeout = TimeSpan.FromSeconds(30);
         method.SetPayloadJson(
             @"{
                 fwPackageUri : 'https://someurl'
             }");
    
         CloudToDeviceMethodResult result = await client.InvokeDeviceMethodAsync(targetDevice, method);
    
         Console.WriteLine("Invoked firmware update on device.");
     }
    
  8. 最后,在 Main 方法中添加以下行:

     registryManager = RegistryManager.CreateFromConnectionString(connString);
     StartFirmwareUpdate().Wait();
     QueryTwinFWUpdateReported().Wait();
     Console.WriteLine("Press ENTER to exit.");
     Console.ReadLine();
    
  9. 在“解决方案资源管理器”中,打开“设置启动项目...”,并确保 TriggerFWUpdate 项目的“操作”为“启动”。

  10. 生成解决方案。

创建模拟设备应用程序

本部分的操作:

  • 创建一个 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. 在 Visual Studio 中,右键单击“TriggerFWUpdate”项目运行 C# 控制台应用,依次选择“调试”和“启动新实例”。

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

    已成功更新固件

后续步骤

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

若要了解如何扩展 IoT 解决方案并在多个设备上计划方法调用,请参阅 Schedule and broadcast jobs (计划和广播作业)教程。