你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程:从后端服务配置设备

作为设备生命周期的一部分,可能需要从后端服务配置 IoT 设备。 将所需配置发送到设备时,还需要从这些设备接收状态和符合性更新。 例如,你可以为设备设置目标作温度范围,或者从设备收集固件版本信息。

若要在设备与 IoT 中心之间同步状态信息,请使用 设备孪生设备孪生是一个 JSON 文档,与特定设备相关联,并由 IoT 中心存储在云中,可在其中查询它们。 设备孪生包含 所需的属性报告的属性标记

  • 所需属性由后端应用程序设置,并由设备读取。
  • 报告的属性由设备设置,后端应用程序读取。
  • 标记由后端应用程序设置,永远不会发送到设备。 使用标记来组织设备。

本教程介绍如何使用所需属性和报告的属性来同步状态信息。

设备端和云端的设备孪生关系图。

将在本教程中执行以下任务:

  • 创建 IoT 中心并将测试设备添加到标识注册表。
  • 使用所需属性将状态信息发送到模拟设备。
  • 使用报告的属性来获取模拟设备的状态信息。

如果没有 Azure 订阅,请在开始之前创建一个免费帐户

先决条件

  • 本教程使用 Azure CLI 创建云资源。 如果您已经拥有注册了设备的 IoT 中心,则可以跳过这些步骤。 有两种方法可以运行 CLI 命令:

  • 本教程中运行的两个示例应用程序是使用 Node.js编写的。 开发计算机上需要 Node.js v10.x.x 或更高版本。

    • 可以从 nodejs.org 下载多个平台的 Node.js

    • 可以使用以下命令验证开发计算机上的当前版本的 Node.js:

      node --version
      
  • 用于 Node.js的 Azure IoT 示例 克隆或下载示例 Node.js 项目。

  • 确保已在防火墙中打开端口 8883。 本教程中的设备示例使用通过端口 8883 进行通信的 MQTT 协议。 某些企业和教育网络环境中可能会阻止此端口。 有关解决此问题的详细信息和方法,请参阅“连接到 IoT 中心”(MQTT)。

设置 Azure 资源

若要完成本教程,Azure 订阅必须包含一个 IoT 中心,并将设备添加到设备标识注册表。 设备标识注册表中的条目使在本教程中运行的模拟设备能够连接到中心。

如果订阅中尚未设置 IoT 中心,可以使用以下 CLI 脚本设置一个。 此脚本使用名称 tutorial-iot-hub ,其中追加了 IoT 中心名称的随机数。 运行它时,可以将此名称替换为你自己的全局唯一名称。 该脚本在 “美国中部 ”区域中创建资源组和中心,可以更改为离你更近的区域。 该脚本将检索您的 IoT 中心服务连接字符串,您可以在后端示例中使用此字符串来连接到您的 IoT 中心。

let "randomIdentifier=$RANDOM*$RANDOM"  
hubname="tutorial-iot-hub-$randomIdentifier"
location=centralus

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

# 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.
# Change the sku to S1 to create a standard-tier hub if necessary.
az iot hub create --name $hubname --location $location --resource-group tutorial-iot-hub-rg --partition-count 2 --sku F1

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

本教程使用名为 MyTwinDevice 的模拟设备。 以下脚本将此设备添加到标识注册表并检索其连接字符串:

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

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

将状态信息发送到设备

使用所需属性将状态信息从后端应用程序发送到设备。 在本部分中,你将了解如何:

  • 将设备配置为接收和处理所需属性。
  • 将所需属性从后端应用程序发送到设备。

所需属性示例

可以采用任何方便应用程序的方式构建所需的属性。 此示例使用一个名为 fanOn 的顶级属性,并将其余属性分组到单独的 组件中。 以下 JSON 代码片段显示了本教程使用的所需属性的结构。 JSON 位于 desired.json 文件中。

{
  "fanOn": "true",
  "components": {
    "system": {
      "id": "17",
      "units": "farenheit",
      "firmwareVersion": "9.75"
    },
    "wifi" : { 
      "channel" : "6",
      "ssid": "my_network"
    },
    "climate" : {
      "minTemperature": "68",
      "maxTemperature": "76"
    }
  }
}

在设备应用程序中接收所需属性

若要查看接收所需属性的模拟设备示例代码,请导航到下载的示例 Node.js 项目中的 iot-hub/Tutorials/DeviceTwins 文件夹。 然后在文本编辑器中打开 SimulatedDevice.js 文件。

以下部分介绍在模拟设备上运行的代码,这些代码响应从后端应用程序发送的所需属性更改。

检索设备孪生对象

将设备注册到 IoT 中心时,会获得设备连接字符串作为输出。 设备使用连接字符串对其在云中的注册标识进行身份验证。 以下代码使用设备连接字符串连接到 IoT 中心:

// Get the device connection string from a command line argument
var connectionString = process.argv[2];

以下代码从客户端对象获取孪生体:

// Get the device twin
client.getTwin(function(err, twin) {
  if (err) {
    console.error(chalk.red('Could not get device twin'));
  } else {
    console.log(chalk.green('Device twin created'));

创建处理程序

可以为所需的属性更新创建处理程序,以响应 JSON 层次结构中不同级别的更新。 例如,此处理程序查看从后端应用程序发送到设备的所有所需属性更改。 delta 变量包含从解决方案后端发送的所需属性:

// Handle all desired property updates
twin.on('properties.desired', function(delta) {
    console.log(chalk.yellow('\nNew desired properties received in patch:'));

以下处理程序仅对 fanOn 所需属性的更改作出反应:

// Handle changes to the fanOn desired property
twin.on('properties.desired.fanOn', function(fanOn) {
    console.log(chalk.green('\nSetting fan state to ' + fanOn));

    // Update the reported property after processing the desired property
    reportedPropertiesPatch.fanOn = fanOn ? fanOn : '{unknown}';
});

多个属性的处理程序

在本教程的示例所需属性 JSON 中,组件下的气候节点包含两个属性:minTemperaturemaxTemperature

设备的本地 孪生 对象存储一组完整的所需属性和报告属性。 从后端发送的 delta 可能只会更新所需属性的某个子集。 在以下代码片段中,如果模拟设备只是收到了对某个 minTemperature 和 maxTemperature 的更新,则它会使用另一个值的本地孪生中的值来配置设备:

// Handle desired properties updates to the climate component
twin.on('properties.desired.components.climate', function(delta) {
    if (delta.minTemperature || delta.maxTemperature) {
      console.log(chalk.green('\nUpdating desired tempertures in climate component:'));
      console.log('Configuring minimum temperature: ' + twin.properties.desired.components.climate.minTemperature);
      console.log('Configuring maximum temperture: ' + twin.properties.desired.components.climate.maxTemperature);

      // Update the reported properties and send them to the hub
      reportedPropertiesPatch.minTemperature = twin.properties.desired.components.climate.minTemperature;
      reportedPropertiesPatch.maxTemperature = twin.properties.desired.components.climate.maxTemperature;
      sendReportedProperties();
    }
});

处理插入、更新和删除操作

从后端发送的期望属性不指示正在对特定期望属性执行的操作。 代码需要根据当前存储在本地的所需属性集,以及中心发送的更改来推断操作。

以下代码片段演示模拟设备如何处理所需属性中 组件 列表中的插入、更新和删除作。 可以看到如何使用 null 值来指示应删除组件:

// Keep track of all the components the device knows about
var componentList = {};

// Use this componentList list and compare it to the delta to infer
// if anything was added, deleted, or updated.
twin.on('properties.desired.components', function(delta) {
  if (delta === null) {
    componentList = {};
  }
  else {
    Object.keys(delta).forEach(function(key) {

      if (delta[key] === null && componentList[key]) {
        // The delta contains a null value, and the
        // device has a record of this component.
        // Must be a delete operation.
        console.log(chalk.green('\nDeleting component ' + key));
        delete componentList[key];

      } else if (delta[key]) {
        if (componentList[key]) {
          // The delta contains a component, and the
          // device has a record of it.
          // Must be an update operation.
          console.log(chalk.green('\nUpdating component ' + key + ':'));
          console.log(JSON.stringify(delta[key]));
          // Store the complete object instead of just the delta
          componentList[key] = twin.properties.desired.components[key];

        } else {
          // The delta contains a component, and the
          // device has no record of it.
          // Must be an add operation.
          console.log(chalk.green('\nAdding component ' + key + ':'));
          console.log(JSON.stringify(delta[key]));
          // Store the complete object instead of just the delta
          componentList[key] = twin.properties.desired.components[key];
        }
      }
    });
  }
});

从后端应用程序发送所需属性

你已了解设备如何实现用于接收所需属性更新的处理程序。 本部分介绍如何从后端应用程序将所需属性更改发送到设备。

若要查看接收所需属性的模拟设备示例代码,请导航到下载的示例 Node.js 项目中的 iot-hub/Tutorials/DeviceTwins 文件夹。 然后在文本编辑器中打开 ServiceClient.js 文件。

以下代码片段演示如何连接到设备标识注册表并访问特定设备的孪生体:

// Create a device identity registry object
var registry = Registry.fromConnectionString(connectionString);

// Get the device twin and send desired property update patches at intervals.
// Print the reported properties after some of the desired property updates.
registry.getTwin(deviceId, async (err, twin) => {
  if (err) {
    console.error(err.message);
  } else {
    console.log('Got device twin');

以下片段演示后端应用程序发送到设备的不同所需属性补丁:

// Turn the fan on
var twinPatchFanOn = {
  properties: {
    desired: {
      patchId: "Switch fan on",
      fanOn: "false",
    }
  }
};

// Set the maximum temperature for the climate component
var twinPatchSetMaxTemperature = {
  properties: {
    desired: {
      patchId: "Set maximum temperature",
      components: {
        climate: {
          maxTemperature: "92"
        }
      }
    }
  }
};

// Add a new component
var twinPatchAddWifiComponent = {
  properties: {
    desired: {
      patchId: "Add WiFi component",
      components: {
        wifi: { 
          channel: "6",
          ssid: "my_network"
        }
      }
    }
  }
};

// Update the WiFi component
var twinPatchUpdateWifiComponent = {
  properties: {
    desired: {
      patchId: "Update WiFi component",
      components: {
        wifi: { 
          channel: "13",
          ssid: "my_other_network"
        }
      }
    }
  }
};

// Delete the WiFi component
var twinPatchDeleteWifiComponent = {
  properties: {
    desired: {
      patchId: "Delete WiFi component",
      components: {
        wifi: null
      }
    }
  }
};

以下代码片段演示后端应用程序如何将所需属性更新发送到设备:

// Send a desired property update patch
async function sendDesiredProperties(twin, patch) {
  twin.update(patch, (err, twin) => {
    if (err) {
      console.error(err.message);
    } else {
      console.log(chalk.green(`\nSent ${twin.properties.desired.patchId} patch:`));
      console.log(JSON.stringify(patch, null, 2));
    }
  });
}

从设备接收状态信息

您的后端应用程序通过设备报告的属性接收状态信息。 设备设置报告的属性,并将其发送到集线器。 后端应用程序可以从中心内存储的设备孪生读取报告属性的当前值。

从设备发送报告属性

可以补丁的形式发送对报告属性值所做的更新。 以下代码片段显示了模拟设备发送的修补程序的模板。 模拟设备在将补丁发送到中心之前更新补丁中的字段。

// Create a patch to send to the hub
var reportedPropertiesPatch = {
  firmwareVersion:'1.2.1',
  lastPatchReceivedId: '',
  fanOn:'',
  minTemperature:'',
  maxTemperature:''
};

模拟设备使用以下函数将包含报告属性的修补程序发送到中心:

// Send the reported properties patch to the hub
function sendReportedProperties() {
  twin.properties.reported.update(reportedPropertiesPatch, function(err) {
    if (err) throw err;
    console.log(chalk.blue('\nTwin state reported'));
    console.log(JSON.stringify(reportedPropertiesPatch, null, 2));
  });
}

处理报告属性

后端应用程序通过设备孪生访问设备的当前报告属性值。 以下代码片段演示后端应用程序如何读取模拟设备的报告属性值:

// Display the reported properties from the device
function printReportedProperties(twin) {
  console.log("Last received patch: " + twin.properties.reported.lastPatchReceivedId);
  console.log("Firmware version: " + twin.properties.reported.firmwareVersion);
  console.log("Fan status: " + twin.properties.reported.fanOn);
  console.log("Min temperature set: " + twin.properties.reported.minTemperature);
  console.log("Max temperature set: " + twin.properties.reported.maxTemperature);
}

运行应用程序

在本部分中,将运行两个示例应用程序来观察后端应用程序向模拟设备应用程序发送所需的属性更新。

若要运行模拟设备和后端应用程序,需要设备和服务连接字符串。 在本教程开始时创建资源时,记下了连接字符串。

若要运行模拟设备应用程序,请打开 shell 或命令提示符窗口,并导航到下载的 Node.js 项目中的 iot-hub/Tutorials/DeviceTwins 文件夹。 然后运行以下命令:

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

若要运行后端应用程序,请打开另一个 shell 或命令提示符窗口。 然后导航到下载的 Node.js 项目中的 iot-hub/Tutorials/DeviceTwins 文件夹。 然后运行以下命令:

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

观察期望的属性更新

以下屏幕截图显示了模拟设备应用程序的输出,并突出显示了它如何处理 maxTemperature 所需属性的更新。 可以看到顶级处理程序和气候组件处理程序如何运行:

显示顶级处理程序和气候组件处理程序如何运行的屏幕截图。

以下屏幕截图显示了后端应用程序的输出,并突出显示了如何将更新发送到 maxTemperature 所需属性:

显示后端应用程序的输出并突出显示其发送更新方式的屏幕截图。

观察已报告的属性更新

以下屏幕截图显示了模拟设备应用程序的输出,并突出显示了如何将报告的属性更新发送到中心:

显示模拟设备更新其孪生状态的屏幕截图。

以下屏幕截图显示了后端应用程序的输出,并突出显示了它如何从设备接收和处理报告的属性更新:

显示接收设备报告属性的后端应用程序的屏幕截图。

清理资源

如果打算完成下一教程,请保留资源组和 IoT 中心,以便稍后重复使用它们。

如果不再需要 IoT 中心,请将其删除,并在门户中删除资源组。 为此,请选择包含您的 IoT 中心的 tutorial-iot-hub-rg 资源组,然后选择删除

或者,使用 CLI:

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

后续步骤

本教程介绍了如何在设备与 IoT 中心之间同步状态信息。 请继续学习下一教程,了解如何使用设备孪生来实现设备更新过程。