您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.

教程:实现设备固件更新过程Tutorial: Implement a device firmware update process

可能需要更新连接到 IoT 中心的设备上的固件。You may need to update the firmware on the devices connected to your IoT hub. 例如,可能需要向固件添加新功能,或者需要应用安全修补程序。For example, you might want to add new features to the firmware or apply security patches. 在许多 IoT 方案中,以物理方式访问设备并以手动方式对设备应用固件更新是不现实的。In many IoT scenarios, it's impractical to physically visit and then manually apply firmware updates to your devices. 本教程介绍如何通过连接到中心的后端应用程序以远程方式启动并监视固件更新过程。This tutorial shows how you can start and monitor the firmware update process remotely through a back-end application connected to your hub.

为了创建并监视固件更新过程,本教程中的后端应用程序在 IoT 中心创建了一个配置。To create and monitor the firmware update process, the back-end application in this tutorial creates a configuration in your IoT hub. IoT 中心自动设备管理使用此配置在所有冷却器设备上更新一组设备孪生所需属性。 IoT Hub automatic device management uses this configuration to update a set of device twin desired properties on all your chiller devices. 所需属性指定所需固件更新的详细信息。The desired properties specify the details of the firmware update that's required. 冷却器设备在运行固件更新过程时,会使用设备孪生报告属性将其状态报告给后端应用程序。 While the chiller devices are running the firmware update process, they report their status to the back-end application using device twin reported properties. 后端应用程序可以使用此配置监视从设备发送的报告属性,并跟踪固件更新过程至完成:The back-end application can use the configuration to monitor the reported properties sent from the device and track the firmware update process to completion:

固件更新过程

在本教程中,请完成以下任务:In this tutorial, you complete the following tasks:

  • 创建 IoT 中心并将测试设备添加到设备标识注册表。Create an IoT hub and add a test device to the device identity registry.
  • 创建用于定义固件更新的配置。Create a configuration to define the firmware update.
  • 模拟设备上的固件更新过程。Simulate the firmware update process on a device.
  • 在固件更新进行过程中从设备接收状态更新。Receive status updates from the device as the firmware update progresses.

使用 Azure Cloud ShellUse Azure Cloud Shell

Azure 托管 Azure Cloud Shell(一个可通过浏览器使用的交互式 shell 环境)。Azure hosts Azure Cloud Shell, an interactive shell environment that you can use through your browser. 通过 Cloud Shell 可以将 bashPowerShell 与 Azure 服务配合使用。Cloud Shell lets you use either bash or PowerShell to work with Azure services. 可以使用 Azure Cloud Shell 预安装的命令来运行本文中的代码,而不必在本地环境中安装任何内容。You can use the Cloud Shell pre-installed commands to run the code in this article without having to install anything on your local environment.

若要启动 Azure Cloud Shell,请执行以下操作:To launch Azure Cloud Shell:

选项Option 示例/链接Example/Link
选择代码块右上角的“试用”。 Select Try It in the upper-right corner of a code block. 选择“试用” 不会自动将代码复制到 Cloud Shell。Selecting Try It doesn't automatically copy the code to Cloud Shell. Azure Cloud Shell 的“试用”示例
转到 https://shell.azure.com 或选择“启动 Cloud Shell” 按钮可在浏览器中打开 Cloud Shell。Go to https://shell.azure.com or select the Launch Cloud Shell button to open Cloud Shell in your browser. 在新窗口中启动 Cloud ShellLaunch Cloud Shell in a new window
选择 Azure 门户右上方菜单栏中的“Cloud Shell” 按钮。Select the Cloud Shell button on the top-right menu bar in the Azure portal. Azure 门户中的“Cloud Shell”按钮

若要在 Azure Cloud Shell 中运行本文中的代码,请执行以下操作:To run the code in this article in Azure Cloud Shell:

  1. 启动 Cloud Shell。Launch Cloud Shell.
  2. 选择代码块上的“复制”按钮 以复制代码。Select the Copy button on a code block to copy the code.
  3. 在 Windows 和 Linux 上使用 Ctrl+Shift+V 将代码粘贴到 Cloud Shell 会话中,或在 macOS 上使用 Cmd+Shift+V 将代码粘贴到 Cloud Shell 会话中。Paste the code into the Cloud Shell session with Ctrl+Shift+V on Windows and Linux, or Cmd+Shift+V on macOS.
  4. Enter 运行此代码。Press Enter to run the code.

如果还没有 Azure 订阅,可以在开始前创建一个免费帐户If you don’t have an Azure subscription, create a free account before you begin.

先决条件Prerequisites

本快速入门中运行的两个示例应用程序是使用 Node.js 编写的。The two sample applications you run in this quickstart are written using Node.js. 开发计算机上需要有 Node.js v10.x.x 或更高版本。You need Node.js v10.x.x or later on your development machine.

可从 nodejs.org 为下载适用于多个平台的 Node.js。You can download Node.js for multiple platforms from nodejs.org.

可以使用以下命令验证开发计算机上 Node.js 当前的版本:You can verify the current version of Node.js on your development machine using the following command:

node --version

https://github.com/Azure-Samples/azure-iot-samples-node/archive/master.zip 下载示例 Node.js 项目并提取 ZIP 存档。Download the sample Node.js project from https://github.com/Azure-Samples/azure-iot-samples-node/archive/master.zip and extract the ZIP archive.

设置 Azure 资源Set up Azure resources

若要完成本教程,你的 Azure 订阅必须有一个 IoT 中心,其中的某个设备已添加到设备标识注册表。To complete this tutorial, your Azure subscription must have an IoT hub with a device added to the device identity registry. 在本教程中运行的模拟设备可以通过设备标识注册表中的条目连接到中心。The entry in the device identity registry enables the simulated device you run in this tutorial to connect to your hub.

如果尚未在订阅中设置 IoT 中心,可使用以下 CLI 脚本设置一个。If you don't already have an IoT hub set up in your subscription, you can set one up with following CLI script. 此脚本使用 tutorial-iot-hub 作为 IoT 中心的名称。运行此脚本时,应将此名称替换为自己的唯一名称。This script uses the name tutorial-iot-hub for the IoT hub, you should replace this name with your own unique name when you run it. 此脚本在“美国中部”区域创建资源组和中心。可以更改为更靠近自己的区域。 The script creates the resource group and hub in the Central US region, which you can change to a region closer to you. 此脚本将检索 IoT 中心服务连接字符串。在后端示例应用程序中,请使用该连接字符串连接到 IoT 中心:The script retrieves your IoT hub service connection string, which you use in the back-end sample application to connect to your IoT hub:

hubname=tutorial-iot-hub
location=centralus

# Install the IoT extension if it's not already installed
az extension add --name azure-cli-iot-ext

# Create a resource group
az group create --name tutorial-iot-hub-rg --location $location

# Create your free-tier IoT Hub. You can only have one free IoT Hub per subscription
az iot hub create --name $hubname --location $location --resource-group tutorial-iot-hub-rg --sku F1

# Make a note of the service connection string, you need it later
az iot hub show-connection-string --name $hubname -policy-name service -o table

本教程使用名为 MyFirmwareUpdateDevice 的模拟设备。This tutorial uses a simulated device called MyFirmwareUpdateDevice. 以下脚本将此设备添加到设备标识注册表、设置标记值,并检索其连接字符串:The following script adds this device to your device identity registry, sets a tag value, and retrieves its connection string:

# Set the name of your IoT hub
hubname=tutorial-iot-hub

# Create the device in the identity registry
az iot hub device-identity create --device-id MyFirmwareUpdateDevice --hub-name $hubname --resource-group tutorial-iot-hub-rg

# Add a device type tag
az iot hub device-twin update --device-id MyFirmwareUpdateDevice --hub-name $hubname --set tags='{"devicetype":"chiller"}'

# Retrieve the device connection string, you need this later
az iot hub device-identity show-connection-string --device-id MyFirmwareUpdateDevice --hub-name $hubname --resource-group tutorial-iot-hub-rg -o table

提示

如果在 Windows 命令提示符或 Powershell 提示符处运行这些命令,请查看 azure-iot-cli-extension 提示页,了解如何引用 JSON 字符串。If you run these commands at a Windows command prompt or Powershell prompt, see the azure-iot-cli-extension tips page for information about how to quote JSON strings.

启动固件更新Start the firmware update

在后端应用程序中创建自动设备管理配置即可在其 devicetype 标记为冷却器的所有设备上开始固件更新过程。You create an automatic device management configuration in the back-end application to begin the firmware update process on all devices tagged with a devicetype of chiller. 本部分介绍以下操作:In this section, you see how to:

  • 在后端应用程序中创建配置。Create a configuration from a back-end application.
  • 监视作业,直至其完成。Monitor the job to completion.

使用所需属性启动从后端应用程序进行的固件升级Use desired properties to start the firmware upgrade from the back-end application

若要查看用于创建配置的后端应用程序代码,请导航到已下载的示例 Node.js 项目中的 iot-hub/Tutorials/FirmwareUpdate 文件夹。To view the back-end application code that creates the configuration, navigate to the iot-hub/Tutorials/FirmwareUpdate folder in the sample Node.js project you downloaded. 然后在文本编辑器中打开 ServiceClient.js 文件。Then open the ServiceClient.js file in a text editor.

后端应用程序创建以下配置:The back-end application creates the following configuration:

var firmwareConfig = {
  id: sampleConfigId,
  content: {
    deviceContent: {
      'properties.desired.firmware': {
        fwVersion: fwVersion,
        fwPackageURI: fwPackageURI,
        fwPackageCheckValue: fwPackageCheckValue
      }
    }
  },

  // Maximum of 5 metrics per configuration
  metrics: {
    queries: {
      current: 'SELECT deviceId FROM devices WHERE configurations.[[firmware285]].status=\'Applied\' AND properties.reported.firmware.fwUpdateStatus=\'current\'',
      applying: 'SELECT deviceId FROM devices WHERE configurations.[[firmware285]].status=\'Applied\' AND ( properties.reported.firmware.fwUpdateStatus=\'downloading\' OR properties.reported.firmware.fwUpdateStatus=\'verifying\' OR properties.reported.firmware.fwUpdateStatus=\'applying\')',
      rebooting: 'SELECT deviceId FROM devices WHERE configurations.[[firmware285]].status=\'Applied\' AND properties.reported.firmware.fwUpdateStatus=\'rebooting\'',
      error: 'SELECT deviceId FROM devices WHERE configurations.[[firmware285]].status=\'Applied\' AND properties.reported.firmware.fwUpdateStatus=\'error\'',
      rolledback: 'SELECT deviceId FROM devices WHERE configurations.[[firmware285]].status=\'Applied\' AND properties.reported.firmware.fwUpdateStatus=\'rolledback\''
    }
  },

  // Specify the devices the firmware update applies to
  targetCondition: 'tags.devicetype = \'chiller\'',
  priority: 20
};

该配置包括以下节:The configuration includes the following sections:

  • content 指定发送到选定设备的固件所需属性。content specifies the firmware desired properties sent to the selected devices.
  • metrics 指定要运行的用于报告固件更新状态的查询。metrics specifies the queries to run that report the status of the firmware update.
  • targetCondition 选择用于接收固件更新的设备。targetCondition selects the devices to receive the firmware update.
  • priorty 设置此配置相对于其他配置的优先级。priorty sets the relative priority of this configuration to other configurations.

后端应用程序使用以下代码创建配置,以便设置所需的属性:The back-end application uses the following code to create the configuration to set the desired properties:

var createConfiguration = function(done) {
  console.log();
  console.log('Add new configuration with id ' + firmwareConfig.id + ' and priority ' + firmwareConfig.priority);

  registry.addConfiguration(firmwareConfig, function(err) {
    if (err) {
      console.log('Add configuration failed: ' + err);
      done();
    } else {
      console.log('Add configuration succeeded');
      done();
    }
  });
};

创建配置后,应用程序会监视固件更新:After it creates the configuration, the application monitors the firmware update:

var monitorConfiguration = function(done) {
  console.log('Monitor metrics for configuration: ' + sampleConfigId);
  setInterval(function(){
    registry.getConfiguration(sampleConfigId, function(err, config) {
      if (err) {
        console.log('getConfiguration failed: ' + err);
      } else {
        console.log('System metrics:');
        console.log(JSON.stringify(config.systemMetrics.results, null, '  '));
        console.log('Custom metrics:');
        console.log(JSON.stringify(config.metrics.results, null, '  '));
      }
    });
  }, 20000);
  done();
};

配置报告两类指标:A configuration reports two types of metrics:

  • 一是系统指标,用于报告针对的设备数目以及应用了更新的设备数目。System metrics that report how many devices are targeted and how many devices have the update applied.
  • 二是自定义指标,由添加到配置的查询生成。Custom metrics generated by the queries you add to the configuration. 在本教程中,查询会报告在更新过程的每个阶段有多少设备。In this tutorial, the queries report how many devices are at each stage of the update process.

响应设备上的固件升级请求Respond to the firmware upgrade request on the device

若要查看模拟设备代码(用于处理从后端应用程序发送的固件所需属性),请导航到已下载的示例 Node.js 项目中的 iot-hub/Tutorials/FirmwareUpdate 文件夹。To view the simulated device code that handles the firmware desired properties sent from the back-end application, navigate to the iot-hub/Tutorials/FirmwareUpdate folder in the sample Node.js project you downloaded. 然后在文本编辑器中打开 SimulatedDevice.js 文件。Then open the SimulatedDevice.js file in a text editor.

模拟设备应用程序为设备孪生中 properties.desired.firmware 所需属性的更新创建一个处理程序。The simulated device application creates a handler for updates to the properties.desired.firmware desired properties in the device twin. 在处理程序中,它会在启动更新过程之前对所需属性执行一些基本的检查:In the handler, it carries out some basic checks on the desired properties before launching the update process:

// Handle firmware desired property updates - this triggers the firmware update flow
twin.on('properties.desired.firmware', function(fwUpdateDesiredProperties) {
  console.log(chalk.green('\nCurrent firmware version: ' + twin.properties.reported.firmware.currentFwVersion));
  console.log(chalk.green('Starting firmware update flow using this data:'));
  console.log(JSON.stringify(fwUpdateDesiredProperties, null, 2));
  desiredFirmwareProperties = twin.properties.desired.firmware;

  if (fwUpdateDesiredProperties.fwVersion == twin.properties.reported.firmware.currentFwVersion) {
    sendStatusUpdate('current', 'Firmware already up to date', function (err) {
      if (err) {
        console.error(chalk.red('Error occured sending status update : ' + err.message));
      }
      return;
    });
  }
  if (fwUpdateInProgress) {
    sendStatusUpdate('current', 'Firmware update already running', function (err) {
      if (err) {
        console.error(chalk.red('Error occured sending status update : ' + err.message));
      }
      return;
    });
  }
  if (!fwUpdateDesiredProperties.fwPackageURI.startsWith('https')) {
    sendStatusUpdate('error', 'Insecure package URI', function (err) {
      if (err) {
        console.error(chalk.red('Error occured sending status update : ' + err.message));
      }
      return;
    });
  }

  fwUpdateInProgress = true;

  sendStartingUpdate(fwUpdateDesiredProperties.fwVersion, function (err) {
    if (err) {
      console.error(chalk.red('Error occured sending starting update : ' + err.message));
    }
    return;
  });
  initiateFirmwareUpdateFlow(function(err, result) {
    fwUpdateInProgress = false;
    if (!err) {
      console.log(chalk.green('Completed firmwareUpdate flow. New version: ' + result));
      sendFinishedUpdate(result, function (err) {
        if (err) {
          console.error(chalk.red('Error occured sending finished update : ' + err.message));
        }
        return;
      });
    }
  }, twin.properties.reported.firmware.currentFwVersion);
});

更新固件Update the firmware

initiateFirmwareUpdateFlow 函数运行更新。The initiateFirmwareUpdateFlow function runs the update. 此函数使用 waterfall 函数按顺序运行更新过程的各个阶段。This function uses the waterfall function to run the phases of the update process in sequence. 在此示例中,固件更新有四个阶段。In this example, the firmware update has four phases. 第一个阶段下载映像,第二个阶段使用校验和验证映像,第三个阶段应用映像,最后一个阶段重启设备:The first phase downloads the image, the second phase verifies the image using a checksum, the third phase applies the image, and the last phase reboots the device:

// Implementation of firmwareUpdate flow
function initiateFirmwareUpdateFlow(callback, currentVersion) {

  async.waterfall([
    downloadImage,
    verifyImage,
    applyImage,
    reboot
  ], function(err, result) {
    if (err) {
      console.error(chalk.red('Error occured firmwareUpdate flow : ' + err.message));
      sendStatusUpdate('error', err.message, function (err) {
        if (err) {
          console.error(chalk.red('Error occured sending status update : ' + err.message));
        }
      });
      setTimeout(function() {
        console.log('Simulate rolling back update due to error');
        sendStatusUpdate('rolledback', 'Rolled back to: ' + currentVersion, function (err) {
          if (err) {
            console.error(chalk.red('Error occured sending status update : ' + err.message));
          }
        });
        callback(err, result);
      }, 5000);
    } else {
      callback(null, result);
    }
  });
}

在更新过程中,模拟设备使用报告属性报告进度:During the update process, the simulated device reports on progress using reported properties:

// Firmware update patch
//  currentFwVersion: The firmware version currently running on the device.
//  pendingFwVersion: The next version to update to, should match what's
//                    specified in the desired properties. Blank if there
//                    is no pending update (fwUpdateStatus is 'current').
//  fwUpdateStatus:   Defines the progress of the update so that it can be
//                    categorized from a summary view. One of:
//     - current:     There is no pending firmware update. currentFwVersion should
//                    match fwVersion from desired properties.
//     - downloading: Firmware update image is downloading.
//     - verifying:   Verifying image file checksum and any other validations.
//     - applying:    Update to the new image file is in progress.
//     - rebooting:   Device is rebooting as part of update process.
//     - error:       An error occurred during the update process. Additional details
//                    should be specified in fwUpdateSubstatus.
//     - rolledback:  Update rolled back to the previous version due to an error.
//  fwUpdateSubstatus: Any additional detail for the fwUpdateStatus . May include
//                     reasons for error or rollback states, or download %.
//
// var twinPatchFirmwareUpdate = {
//   firmware: {
//     currentFwVersion: '1.0.0',
//     pendingFwVersion: '',
//     fwUpdateStatus: 'current',
//     fwUpdateSubstatus: '',
//     lastFwUpdateStartTime: '',
//     lastFwUpdateEndTime: ''
//   }
// };

以下代码片段演示下载阶段的实施。The following snippet shows the implementation of the download phase. 在下载阶段,模拟设备使用报告属性将状态信息发送给后端应用程序:During the download phase, the simulated device uses reported properties to send status information to the back-end application:

// Simulate downloading an image
function downloadImage(callback) {
  console.log('Simulating image download from: ' + desiredFirmwareProperties.fwPackageURI);

  async.waterfall([
    function(callback) {
      sendStatusUpdate('downloading', 'Start downloading', function (err) {
        if (err) {
          console.error(chalk.red('Error occured sending status update : ' + err.message));
        }
      });
      callback(null);
    },
    function(callback) {
      // Simulate a delay downloading the image.
      setTimeout(function() {
        // Simulate some firmware image data
        var imageData = '[Fake firmware image data]';
        callback(null, imageData); 
      }, 30000);
    },
    function(imageData, callback) {
      console.log('Downloaded image data: ' + imageData);
      sendStatusUpdate('downloading', 'Finished downloading', function (err) {
        if (err) {
          console.error(chalk.red('Error occured sending status update : ' + err.message));
        }
      });
      callback(null, imageData);
    }
  ], function (err, result) {
    callback(err, result);
  });
}

运行示例Run the sample

在此部分,请运行两个示例应用程序,以便在后端应用程序创建配置来管理模拟设备上的固件更新过程时对其进行观察。In this section, you run two sample applications to observe as a back-end application creates the configuration to manage the firmware update process on the simulated device.

若要运行模拟设备和后端应用程序,需要使用设备和服务连接字符串。To run the simulated device and back-end applications, you need the device and service connection strings. 在本教程的开头部分创建资源时,我们已记下这些连接字符串。You made a note of the connection strings when you created the resources at the start of this tutorial.

若要运行模拟设备应用程序,请打开 shell 或命令提示符窗口,并导航到下载的 Node.js 项目中的 iot-hub/Tutorials/FirmwareUpdate 文件夹。To run the simulated device application, open a shell or command prompt window and navigate to the iot-hub/Tutorials/FirmwareUpdate folder in the Node.js project you downloaded. 然后运行以下命令:Then run the following commands:

npm install
node SimulatedDevice.js "{your device connection string}"

若要运行后端应用程序,请打开另一个 shell 或命令提示符窗口。To run the back-end application, open another shell or command prompt window. 导航到下载的 Node.js 项目中的 iot-hub/Tutorials/FirmwareUpdate 文件夹。Then navigate to the iot-hub/Tutorials/FirmwareUpdate folder in the Node.js project you downloaded. 然后运行以下命令:Then run the following commands:

npm install
node ServiceClient.js "{your service connection string}"

以下屏幕截图显示模拟设备应用程序的输出,并显示它如何响应后端应用程序提供的固件所需属性更新:The following screenshot shows the output from the simulated device application and shows how it responds to the firmware desired properties update from the back-end application:

模拟设备

以下屏幕截图显示后端应用程序的输出,并突出显示它如何创建配置来更新固件所需属性:The following screenshot shows the output from the back-end application and highlights how it creates the configuration to update the firmware desired properties:

后端应用程序

以下屏幕截图显示后端应用程序的输出,并突出显示它如何监视模拟设备提供的固件更新指标:The following screenshot shows the output from the back-end application and highlights how it monitors the firmware update metrics from the simulated device:

后端应用程序

由于自动设备配置在创建时运行,然后每五分钟运行一次,因此你可能看不到发送到后端应用程序的每个状态更新。Because automatic device configurations run at creation time and then every five minutes, you may not see every status update sent to the back-end application. 也可在门户中查看这些指标,具体说来就是在 IoT 中心的“自动设备管理 -> IoT 设备配置”部分: You can also view the metrics in the portal in the Automatic device management -> IoT device configuration section of your IoT hub:

在门户中查看配置

清理资源Clean up resources

如果你打算完成下一篇教程,请保留资源组和 IoT 中心,以便到时重复使用。If you plan to complete the next tutorial, leave the resource group and IoT hub and reuse them later.

如果不再需要 IoT 中心,请在门户中删除该中心与资源组。If you don't need the IoT hub any longer, delete it and the resource group in the portal. 为此,请选择包含 IoT 中心的 tutorial-iot-hub-rg 资源组,然后单击“删除” 。To do so, select the tutorial-iot-hub-rg resource group that contains your IoT hub and click Delete.

或者使用 CLI:Alternatively, use the CLI:

# Delete your resource group and its contents
az group delete --name tutorial-iot-hub-rg

后续步骤Next steps

本教程介绍了如何针对连接的设备实施固件更新过程。In this tutorial, you learned how to implement a firmware update process for your connected devices. 转到下一教程,了解如何使用 Azure IoT 中心门户工具和 Azure CLI 命令来测试设备连接性。Advance to the next tutorial to learn how to use Azure IoT Hub portal tools and Azure CLI commands to test device connectivity.