使用所需属性配置设备

介绍

IoT 中心设备孪生入门中,已学习了如何使用标记通过解决方案后端设置设备元数据、使用报告属性通过设备应用报告设备条件以及使用类似 SQL 的语言查询此信息。

在本教程中,将了解 如何使用设备孪生的所需属性和报告属性来远程配置设备应用。 具体而言,本教程演示设备孪生的报告属性和所需属性如何启用设备应用程序的多步配置,并跨所有设备向解决方案后端提供此操作状态的可见性。 有关在配置设备时所需角色的详细信息,请参阅使用 IoT 中心进行设备管理概述

简而言之,使用设备孪生时,解决方案后端可以为托管的设备指定所需配置,而不需要发送特定的命令。 此方法让设备负责设置更新其配置状态的最佳方式(对于特定设备条件影响即时执行特定命令的能力的 IoT 方案而言十分重要),同时继续向解决方案后端报告更新过程的当前状态和潜在的错误条件。 此模式能够帮助管理大量设备,因为它让解决方案后端能够跨所有设备完全掌握配置流程状态。

Note

在以更具交互性的方式控制设备的方案中(通过用户控制的应用打开风扇),请考虑使用直接方法

在本教程中,解决方案后端更改目标设备的遥测配置,这样一来,设备应用会执行多步过程来应用配置更新(例如,要求软件模块重启,这在本教程中以简单的延迟进行模拟)。

解决方案后端采用以下方式将配置存储在设备孪生的所需属性中:

    {
        ...
        "properties": {
            ...
            "desired": {
                "telemetryConfig": {
                    "configId": "{id of the configuration}",
                    "sendFrequency": "{config}"
                }
            }
            ...
        }
        ...
    }

Note

由于配置可能是复杂对象,因此会为它们分配唯一 ID(哈希值或 GUIDs),以简化其比较。

设备应用以镜像报告属性中的所需属性 telemetryConfig 的形式报告其当前配置:

    {
        "properties": {
            ...
            "reported": {
                "telemetryConfig": {
                    "changeId": "{id of the current configuration}",
                    "sendFrequency": "{current configuration}",
                    "status": "Success",
                }
            }
            ...
        }
    }

请注意,报告的 telemetryConfig 具有 status 这个其他属性,该属性用于报告配置更新过程的状态。

收到新的所需配置时,设备应用通过更改以下信息报告挂起的配置:

    {
        "properties": {
            ...
            "reported": {
                "telemetryConfig": {
                    "changeId": "{id of the current configuration}",
                    "sendFrequency": "{current configuration}",
                    "status": "Pending",
                    "pendingConfig": {
                        "changeId": "{id of the pending configuration}",
                        "sendFrequency": "{pending configuration}"
                    }
                }
            }
            ...
        }
    }

然后,在稍后的某个时间,设备应用将通过更新上述属性报告此操作的成功或失败状态。 请注意,解决方案后端可以在任何时候跨所有设备查询配置流程的状态。

本教程演示如何:

  • 创建一个模拟设备应用,用于接收来自解决方案后端的配置更新,以及将多个更新作为配置更新过程的报告属性进行报告。
  • 创建一个后端应用,用于更新设备的所需配置,并查询配置更新流程。

在本教程结束时,会创建两个 .NET 控制台应用:

  • SimulateDeviceConfiguration,一个模拟设备应用,它等待所需配置更新并报告模拟配置更新过程的状态。
  • SetDesiredConfigurationAndQuery,一个后端应用,用于在设备上设置所需配置并查询配置更新过程。

Note

Azure IoT SDK 文章介绍了可用于构建设备和后端应用的 Azure IoT SDK。

若要完成本教程,需要满足以下条件:

  • Visual Studio 2015 或 Visual Studio 2017。
  • 有效的 Azure 帐户。 如果没有帐户,可以创建一个试用帐户,只需几分钟即可完成。

如果已按照设备孪生入门教程执行了操作,则现在已有一个 IoT 中心和一个名为 myDeviceId 的设备标识。 在这种情况下,可以跳到创建模拟设备应用部分。

创建 IoT 中心

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

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

    Azure 门户跳转栏

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

    IoT 中心边栏选项卡

    • 在“名称”框中,输入 IoT 中心的名称。 如果该“名称”有效且可用,“名称”框中会出现绿色的勾选标记。

    Important

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

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

    新的 IoT 中心状态

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

    新的 IoT 中心边栏选项卡

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

    共享访问策略边栏选项卡

创建设备标识

在本部分中,将使用 Azure 门户在 IoT 中心的标识注册表中创建设备标识。 设备无法连接到 IoT 中心,除非它在标识注册表中具有条目。 有关详细信息,请参阅 IoT 中心开发人员指南的“标识注册表”部分。 门户中的“Device Explorer”可帮助生成唯一的设备 ID 和密钥,当设备连接到 IoT 中心时,可以使用这些信息进行自我标识。 设备 ID 区分大小写。

  1. 请确保已登录到 Azure 门户

  2. 在跳转栏中单击“所有资源”,然后查找 IoT 中心资源。

    导航到 IoT 中心

  3. IoT 中心资源打开以后,单击“Device Explorer”工具,然后单击顶部的“添加”。 提供新设备的名称(例如 myDeviceId),然后单击“保存”。

    在门户中创建设备标识

    这将为 IoT 中心创建新设备标识。

    Important

    收集的日志中可能会显示设备 ID 用于客户支持和故障排除,因此,在为日志命名时,请务必避免包含任何敏感信息。

  4. 在 Device Explorer 的设备列表中,单击新创建的设备,记下“连接字符串---主键”。

    设备连接字符串

Note

IoT 中心标识注册表仅存储用于实现 IoT 中心安全访问的设备标识。 它存储设备 ID 和密钥作为安全凭据,以及启用/禁用标志让你禁用对单个设备的访问。 如果应用程序需要存储其他特定于设备的元数据,则应使用特定于应用程序的存储。 有关详细信息,请参阅 IoT 中心开发人员指南

创建模拟设备应用

本部分创建一个 .NET 控制台应用,它作为 myDeviceId 连接到中心,等待所需配置更新,然后对模拟配置更新过程报告更新。

  1. 在 Visual Studio 中,使用“控制台应用程序”项目模板创建新的 Visual C# Windows 经典桌面项目。 将项目命名为 SimulateDeviceConfiguration

    新的 Visual C# Windows 经典设备应用

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

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

    “NuGet 包管理器”窗口客户端应用

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

     using Microsoft.Azure.Devices.Client;
     using Microsoft.Azure.Devices.Shared;
     using Newtonsoft.Json;
    
  5. 将以下字段添加到 Program 类。 将占位符值替换为在上一部分中记下的设备连接字符串。

     static string DeviceConnectionString = "HostName=<yourIotHubName>.azure-devices.cn;DeviceId=<yourIotDeviceName>;SharedAccessKey=<yourIotDeviceAccessKey>";
     static DeviceClient Client = null;
     static TwinCollection reportedProperties = new TwinCollection();
    
  6. 将以下方法添加到 Program 类:

     public static void InitClient()
     {
         try
         {
             Console.WriteLine("Connecting to hub");
             Client = DeviceClient.CreateFromConnectionString(DeviceConnectionString, TransportType.Mqtt);
         }
         catch (Exception ex)
         {
             Console.WriteLine();
             Console.WriteLine("Error in sample: {0}", ex.Message);
         }
     }
    

    Client 对象公开从该设备与设备孪生交互所需的所有方法。 上述代码会初始化“客户端”对象,然后检索 myDeviceId设备孪生。

  7. 将以下方法添加到 Program 类。 此方法在本地设备上设置遥测初始值,然后更新设备孪生。

     public static async void InitTelemetry()
     {
         try
         {
             Console.WriteLine("Report initial telemetry config:");
             TwinCollection telemetryConfig = new TwinCollection();
    
             telemetryConfig["configId"] = "0";
             telemetryConfig["sendFrequency"] = "24h";
             reportedProperties["telemetryConfig"] = telemetryConfig;
             Console.WriteLine(JsonConvert.SerializeObject(reportedProperties));
    
             await Client.UpdateReportedPropertiesAsync(reportedProperties);
         }
         catch (AggregateException ex)
         {
             foreach (Exception exception in ex.InnerExceptions)
             {
                 Console.WriteLine();
                 Console.WriteLine("Error in sample: {0}", exception);
             }
         }
         catch (Exception ex)
         {
             Console.WriteLine();
             Console.WriteLine("Error in sample: {0}", ex.Message);
         }
     }
    
  8. 将以下方法添加到 Program 类。 这是一个回调,用于检测设备孪生的“所需属性”中的更改。

     private static async Task OnDesiredPropertyChanged(TwinCollection desiredProperties, object userContext)
     {
         try
         {
             Console.WriteLine("Desired property change:");
             Console.WriteLine(JsonConvert.SerializeObject(desiredProperties));
    
             var currentTelemetryConfig = reportedProperties["telemetryConfig"];
             var desiredTelemetryConfig = desiredProperties["telemetryConfig"];
    
             if ((desiredTelemetryConfig != null) && (desiredTelemetryConfig["configId"] != currentTelemetryConfig["configId"]))
             {
                 Console.WriteLine("\nInitiating config change");
                 currentTelemetryConfig["status"] = "Pending";
                 currentTelemetryConfig["pendingConfig"] = desiredTelemetryConfig;
    
                 await Client.UpdateReportedPropertiesAsync(reportedProperties);
    
                 CompleteConfigChange();
             }
         }
         catch (AggregateException ex)
         {
             foreach (Exception exception in ex.InnerExceptions)
             {
                 Console.WriteLine();
                 Console.WriteLine("Error in sample: {0}", exception);
             }
         }
         catch (Exception ex)
         {
             Console.WriteLine();
             Console.WriteLine("Error in sample: {0}", ex.Message);
         }
     }
    

    此方法使用配置更新请求更新本地设备孪生对象的报告属性,将状态设置为 Pending,然后更新服务的设备孪生。 成功更新设备孪生后,它会通过调用下一要点中所述的方法 CompleteConfigChange 来完成配置更改。

  9. 将以下方法添加到 Program 类。 此方法模拟设备重置,更新将状态设置为 Success 的本地报告属性,并删除 pendingConfig 元素。 然后,它会更新服务的设备孪生。

     public static async void CompleteConfigChange()
     {
         try
         {
             var currentTelemetryConfig = reportedProperties["telemetryConfig"];
    
             Console.WriteLine("\nSimulating device reset");
             await Task.Delay(30000); 
    
             Console.WriteLine("\nCompleting config change");
             currentTelemetryConfig["configId"] = currentTelemetryConfig["pendingConfig"]["configId"];
             currentTelemetryConfig["sendFrequency"] = currentTelemetryConfig["pendingConfig"]["sendFrequency"];
             currentTelemetryConfig["status"] = "Success";
             currentTelemetryConfig["pendingConfig"] = null;
    
             await Client.UpdateReportedPropertiesAsync(reportedProperties);
             Console.WriteLine("Config change complete \nPress any key to exit.");
         }
         catch (AggregateException ex)
         {
             foreach (Exception exception in ex.InnerExceptions)
             {
                 Console.WriteLine();
                 Console.WriteLine("Error in sample: {0}", exception);
             }
         }
         catch (Exception ex)
         {
             Console.WriteLine();
             Console.WriteLine("Error in sample: {0}", ex.Message);
         }
     }
    
  10. 最后,在 Main 方法中添加以下行:

     try
     {
         InitClient();
         InitTelemetry();
    
         Console.WriteLine("Wait for desired telemetry...");
         Client.SetDesiredPropertyUpdateCallback(OnDesiredPropertyChanged, null).Wait();
         Console.ReadKey();
     }
     catch (AggregateException ex)
     {
         foreach (Exception exception in ex.InnerExceptions)
         {
             Console.WriteLine();
             Console.WriteLine("Error in sample: {0}", exception);
         }
     }
     catch (Exception ex)
     {
         Console.WriteLine();
         Console.WriteLine("Error in sample: {0}", ex.Message);
     }
    

    Note

    本教程不模拟并发配置更新的任何行为。 某些配置更新进程在更新运行过程中可能能够适应目标配置的更改,某些配置更新进程则可能必须将它们排队,某些配置更新进程会拒绝它们并显示错误情况。 请确保考虑特定配置过程所需的行为,并在开始配置更改之前添加相应的逻辑。

  11. 生成解决方案,单击 F5 从 Visual Studio 运行设备应用。 在输出控制台中,应会看到消息指示模拟设备正在检索设备孪生、设置遥测,并等待所需属性发生更改。 使应用保持运行状态。

创建服务应用

在本节中,用户需创建一个 .NET 控制台应用,以便通过新的遥测配置对象在与 myDeviceId 关联的设备孪生上更新 所需属性 。 然后,它查询存储在 IoT 中心的设备孪生,并显示该设备的所需配置和报告配置之间的差异。

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

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

  2. 在“解决方案资源管理器”中,右键单击“SetDesiredConfigurationAndQuery”项目,并单击“管理 NuGet 包...”。
  3. 在“NuGet 包管理器”窗口中,选择“浏览”,搜索 microsoft.azure.devices,选择“安装”以安装 Microsoft.Azure.Devices 包,并接受使用条款。 该过程将下载、安装 Azure IoT 服务 SDK NuGet 包及其依赖项并添加对它的引用。

    “NuGet 包管理器”窗口

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

     using Microsoft.Azure.Devices;
     using System.Threading;
     using Newtonsoft.Json;
    
  5. 将以下字段添加到 Program 类。 将占位符值替换为在上一部分中为中心创建的 IoT 中心连接字符串。

     static RegistryManager registryManager;
     static string connectionString = "{iot hub connection string}";
    
  6. 将以下方法添加到 Program 类:

     static private async Task SetDesiredConfigurationAndQuery()
     {
         var twin = await registryManager.GetTwinAsync("myDeviceId");
         var patch = new {
                 properties = new {
                     desired = new {
                         telemetryConfig = new {
                             configId = Guid.NewGuid().ToString(),
                             sendFrequency = "5m"
                         }
                     }
                 }
             };
    
         await registryManager.UpdateTwinAsync(twin.DeviceId, JsonConvert.SerializeObject(patch), twin.ETag);
         Console.WriteLine("Updated desired configuration");
    
         while (true)
         {
             var query = registryManager.CreateQuery("SELECT * FROM devices WHERE deviceId = 'myDeviceId'");
             var results = await query.GetNextAsTwinAsync();
             foreach (var result in results)
             {
                 Console.WriteLine("Config report for: {0}", result.DeviceId);
                 Console.WriteLine("Desired telemetryConfig: {0}", JsonConvert.SerializeObject(result.Properties.Desired["telemetryConfig"], Formatting.Indented));
                 Console.WriteLine("Reported telemetryConfig: {0}", JsonConvert.SerializeObject(result.Properties.Reported["telemetryConfig"], Formatting.Indented));
                 Console.WriteLine();
             }
             Thread.Sleep(10000);
         }
     }
    

    Registry 对象公开从服务与设备孪生进行交互所需的所有方法。 此代码将初始化 Registry 对象,并检索 myDeviceId 的设备孪生,并使用新的遥测配置对象更新其所需属性。 然后,该代码会每隔 10 秒钟查询一次存储在 IoT 中心的设备孪生,并打印所需遥测配置和报告遥测配置。 请参阅 IoT 中心查询语言 以了解如何跨所有设备生成丰富的报告。

    Important

    为了方便用户查看,此应用程序每 10 秒查询 IoT 中心一次。 使用查询跨多个设备生成面向用户的报表,而不检测更改。 如果解决方案需要设备事件的实时通知,请使用孪生通知

  7. 最后,在 Main 方法中添加以下行:

     registryManager = RegistryManager.CreateFromConnectionString(connectionString);
     SetDesiredConfigurationAndQuery();
     Console.WriteLine("Press any key to quit.");
     Console.ReadLine();
    
  8. 在“解决方案资源管理器”中,打开“设置启动项目...”,并确保 SetDesiredConfigurationAndQuery 项目的“操作”为“启动”。 生成解决方案。
  9. 在运行 SimulateDeviceConfiguration 设备应用的情况下,使用 F5 从 Visual Studio 运行服务应用。 应会看到报告的配置从 Pending 更改为 Success,此时新的活动发送频率已变为 5 分钟而不是 24 小时。

    已成功配置设备

    Important

    设备报告操作与查询结果之间最多存在一分钟的延迟。 这是为了使查询基础结构可以采用非常大的规模来工作。 若要检索单个设备孪生的一致视图,请使用 Registry 类中的 getDeviceTwin 方法。

后续步骤

在本教程中,用户已从解决方案后端将所需配置设置为 所需属性 ,此外还编写了一个设备应用来检测该更改并模拟多步骤更新过程(通过报告属性报告其状态)。

充分利用以下资源:

  • 通过 Get started with IoT Hub (IoT 中心入门)教程学习如何从设备发送遥测;
  • 关于对大型设备集进行计划或执行操作,请参阅 计划和广播作业 教程。
  • 通过使用直接方法教程学习如何以交互方式控制设备(例如从用户控制的应用打开风扇)。