使用设备管理启动设备固件更新 (.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 版本 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 中心可能需要数分钟的时间。 可在“通知”窗格中监视进度。

创建 IoT 中心以后,即可找到将设备和应用程序连接到 IoT 中心时需要使用的重要信息。

  1. 成功创建 IoT 中心后,请在 Azure 门户中单击 IoT 中心对应的新磁贴,以打开新 IoT 中心的属性窗口。 记下“主机名”,并单击“共享访问策略”。

    新建 IoT 中心窗口

  2. 在“共享访问策略”中,单击“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 中心入门

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

在本部分中,用户创建一个 .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”项目,依次选择“调试”和“启动新实例”。

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

    已成功更新固件

后续步骤

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

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