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

使用设备预配服务 (DPS) 自动管理 Azure 数字孪生中的设备

在本文中,你将学习如何将 Azure 数字孪生与设备预配服务 (DPS) 集成。

通过本文中所述的解决方案,可以使用设备预配服务自动执行进程,在 Azure 数字孪生中预配和停用 IoT 中心设备。

有关预配和停用阶段的详细信息,以及如何更好地了解所有企业 IoT 项目通用的一系列常规设备管理阶段,请参阅 IoT 中心设备管理文档的设备生命周期部分

先决条件

在设置预配之前,需要设置以下资源:

  • Azure 数字孪生实例。 请按照设置实例和身份验证中的说明创建 Azure 数字孪生实例。 在 Azure 门户中收集实例的主机名说明)。
  • 一个 IoT 中心。 有关说明,请参阅此 IoT 中心快速入门中的“创建 IoT 中心”部分。
  • 基于 IoT 中心数据更新数字孪生信息的 Azure 函数。 请按照引入 IoT 中心数据中的说明创建此 Azure 函数。 收集函数名称以便在本文中使用

此示例还使用了设备模拟器,其中包括使用设备预配服务的预配。 设备模拟器位于此处:Azure 数字孪生和 IoT 中心集成示例。 通过导航到示例的 GitHub 存储库获取计算机上的示例项目,可以通过选择 “代码 ”按钮并 下载 ZIP,将其下载为 .zip 文件。

Screenshot of the digital-twins-iothub-integration repo on GitHub, highlighting the steps to download it as a zip.

解压缩下载的文件夹。

需要在计算机上安装 Node.js。 设备模拟器基于 Node.js 版本 10.0. x 或更高版本。

解决方案体系结构

此解决方案包括使用设备预配服务在 Azure 数字孪生中预配和停用设备的步骤。

为了在解决方案中分配设备,数据在恒温设备和 DPS 之间流动。 然后,数据从 DPS 流入 IoT 中心,并通过 Azure 函数传输到 Azure 数字孪生。

若要停用设备,手动删除设备产生的数据通过 IoT 中心、事件中心和 Azure 函数流入 Azure 数字孪生。

此图演示了此体系结构。

Diagram of device and several Azure services in an end-to-end scenario showing the data flow.

本文分为两个部分,每个部分关注此完整体系结构的一部分:

使用设备预配服务自动预配设备

在此部分,你要将设备预配服务附加到 Azure 数字孪生,通过以下路径自动预配设备。 此示意图取自前面所示的完整体系结构。

Diagram of Provision flow—an excerpt of the solution architecture diagram following data from a thermostat into Azure Digital Twins.

下面是流程说明:

  1. 设备联系 DPS 终结点,传递标识信息以证明其身份。
  2. DPS 通过对照注册列表验证注册 ID 和密钥来验证设备身份,并调用 Azure 函数进行分配。
  3. Azure 函数在 Azure 数字孪生中为设备创建一个新孪生体。 该孪生体的名称与设备的注册 ID 相同。
  4. DPS 将设备注册到 IoT 中心,并填充设备所选的孪生状态。
  5. IoT 中心向设备返回设备 ID 信息和 IoT 中心连接信息。 现在,设备可以连接到 IoT 中心。

下面部分将逐步讲解设置此自动预配设备流的步骤。

创建设备预配服务

使用设备预配服务预配新设备时,可以在 Azure 数字孪生中为该设备创建一个名称与注册 ID 相同的新孪生体。

创建一个用于预配 IoT 设备的设备预配服务实例。 可以使用以下 Azure CLI 说明,也可以按照使用 Azure 门户设置 IoT 中心设备预配服务中的说明使用 Azure 门户。

以下 Azure CLI 命令将创建设备预配服务。 你需要指定设备预配服务名称、资源组和区域。 要查看哪些区域支持设备预配服务,请访问各区域可用的 Azure 产品。 此命令可在 Cloud Shell 中运行,如果计算机中已安装 Azure CLI,也可在本地运行。

az iot dps create --name <Device-Provisioning-Service-name> --resource-group <resource-group-name> --location <region>

添加用于设备预配服务的函数

先决条件部分创建的函数应用项目中,创建一个新函数以用于设备预配服务。 设备预配服务将在自定义分配策略中使用此函数来预配新设备。

导航到计算机的函数应用项目,然后执行以下步骤。

  1. 首先,在函数应用项目中创建一个 HTTP 触发器类型的新函数

  2. 向项目中添加一个新 NuGet 包:Microsoft.Azure.Devices.Provisioning.Service。 如果代码中使用的包尚不是项目的一部分,则可能还需要向项目中添加更多包。

  3. 在新建的函数代码文件中,粘贴以下代码,将该函数重命名为 DpsAdtAllocationFunc.cs,然后保存该文件。

    // Copyright (c) Microsoft. All rights reserved.
    // Licensed under the MIT license. See LICENSE file in the project root for full license information.
    
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using Azure;
    using Azure.Core.Pipeline;
    using Azure.DigitalTwins.Core;
    using Azure.Identity;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.Http;
    using Microsoft.Azure.Devices.Shared;
    using Microsoft.Azure.Devices.Provisioning.Service;
    using Microsoft.Extensions.Logging;
    using Newtonsoft.Json;
    
    namespace Samples.AdtIothub
    {
        public static class DpsAdtAllocationFunc
        {
            private static string adtInstanceUrl = Environment.GetEnvironmentVariable("ADT_SERVICE_URL");
            private static readonly HttpClient singletonHttpClientInstance = new HttpClient();
    
            [FunctionName("DpsAdtAllocationFunc")]
            public static async Task<IActionResult> Run(
                [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
            {
                // Get request body
                string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
                log.LogDebug($"Request.Body: {requestBody}");
                dynamic data = JsonConvert.DeserializeObject(requestBody);
    
                // Get registration ID of the device
                string regId = data?.deviceRuntimeContext?.registrationId;
    
                bool fail = false;
                string message = "Uncaught error";
                var response = new ResponseObj();
    
                // Must have unique registration ID on DPS request
                if (regId == null)
                {
                    message = "Registration ID not provided for the device.";
                    log.LogInformation("Registration ID: NULL");
                    fail = true;
                }
                else
                {
                    string[] hubs = data?.linkedHubs.ToObject<string[]>();
    
                    // Must have hubs selected on the enrollment
                    if (hubs == null
                        || hubs.Length < 1)
                    {
                        message = "No hub group defined for the enrollment.";
                        log.LogInformation("linkedHubs: NULL");
                        fail = true;
                    }
                    else
                    {
                        // Find or create twin based on the provided registration ID and model ID
                        dynamic payloadContext = data?.deviceRuntimeContext?.payload;
                        string dtmi = payloadContext.modelId;
                        log.LogDebug($"payload.modelId: {dtmi}");
                        string dtId = await FindOrCreateTwinAsync(dtmi, regId, log);
    
                        // Get first linked hub (TODO: select one of the linked hubs based on policy)
                        response.iotHubHostName = hubs[0];
    
                        // Specify the initial tags for the device.
                        var tags = new TwinCollection();
                        tags["dtmi"] = dtmi;
                        tags["dtId"] = dtId;
    
                        // Specify the initial desired properties for the device.
                        var properties = new TwinCollection();
    
                        // Add the initial twin state to the response.
                        var twinState = new TwinState(tags, properties);
                        response.initialTwin = twinState;
                    }
                }
    
                log.LogDebug("Response: " + ((response.iotHubHostName != null)? JsonConvert.SerializeObject(response) : message));
    
                return fail
                    ? new BadRequestObjectResult(message)
                    : (ActionResult)new OkObjectResult(response);
            }
    
            public static async Task<string> FindOrCreateTwinAsync(string dtmi, string regId, ILogger log)
            {
                // Create Digital Twins client
                var cred = new DefaultAzureCredential();
                var client = new DigitalTwinsClient(new Uri(adtInstanceUrl), cred);
    
                // Find existing DigitalTwin with registration ID
                try
                {
                    // Get DigitalTwin with Id 'regId'
                    BasicDigitalTwin existingDt = await client.GetDigitalTwinAsync<BasicDigitalTwin>(regId).ConfigureAwait(false);
    
                    // Check to make sure it is of the correct model type
                    if (StringComparer.OrdinalIgnoreCase.Equals(dtmi, existingDt.Metadata.ModelId))
                    {
                        log.LogInformation($"DigitalTwin {existingDt.Id} already exists");
                        return existingDt.Id;
                    }
    
                    // Found DigitalTwin but it is not of the correct model type
                    log.LogInformation($"Found DigitalTwin {existingDt.Id} but it is not of model {dtmi}");
                }
                catch(RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.NotFound)
                {
                    log.LogDebug($"Did not find DigitalTwin {regId}");
                }
    
                // Either the DigitalTwin was not found, or we found it but it is of a different model type
                // Create or replace it with what it needs to be, meaning if it was not found a brand new DigitalTwin will be created
                // and if it was of a different model, it will replace that existing DigitalTwin
                // If it was intended to only create the DigitalTwin if there is no matching DigitalTwin with the same Id,
                // ETag.All could have been used as the ifNonMatch parameter to the CreateOrReplaceDigitalTwinAsync method call.
                // Read more in the CreateOrReplaceDigitalTwinAsync documentation here:
                // https://docs.microsoft.com/en-us/dotnet/api/azure.digitaltwins.core.digitaltwinsclient.createorreplacedigitaltwinasync?view=azure-dotnet
                BasicDigitalTwin dt = await client.CreateOrReplaceDigitalTwinAsync(
                    regId, 
                    new BasicDigitalTwin
                    {
                        Metadata = { ModelId = dtmi },
                        Contents = 
                        {
                            { "Temperature", 0.0 }
                        }
                    }
                ).ConfigureAwait(false);
    
                log.LogInformation($"Digital Twin {dt.Id} created.");
                return dt.Id;
            }
        }
    
        /// <summary>
        /// Expected function result format
        /// </summary>
        public class ResponseObj
        {
            public string iotHubHostName { get; set; }
            public TwinState initialTwin { get; set; }
        }
    }
    
  4. 将包含 DpsAdtAllocationFunc.cs 函数的项目发布到 Azure 中的函数应用

    若要了解如何使用 Visual Studio 发布函数,请参阅使用 Visual Studio 开发 Azure Functions。 若要了解如何使用 Visual Studio Code 发布函数,请参阅使用 Visual Studio Code 在 Azure 中创建 C# 函数。 若要了解如何使用 Azure CLI 发布函数,请参阅在 Azure 中从命令行创建 C# 函数

重要

先决条件部分首次创建函数应用时,你可能已经为该函数分配了访问角色,并为其配置了访问 Azure 数字孪生实例所需的应用程序设置。 这些操作需要针对整个函数应用执行一次,因此在继续之前,请验证是否已在你的应用中完成这些操作。 可以在编写应用验证码一文的配置已发布的应用部分找到相关说明。

创建设备预配注册

接下来,需要使用自定义分配函数在设备预配服务中创建注册。 若要创建注册,请按照设备预配服务文档中“自定义分配策略”文章的创建注册部分的说明进行操作。

执行该流时,请确保选择以下选项,以将注册链接到创建的函数。

  • 选择将设备分配到中心的方式:自定义(使用 Azure 函数)。
  • 选择可将此组分配到的 IoT 中心: 选择 IoT 中心名称,或选择 “链接新的 IoT 中心 ”按钮,然后从选项中选择 IoT 中心。

接下来,选择“选择新函数”按钮,将你的函数应用链接到注册组。 然后,填充以下值:

  • 订阅:Azure 订阅已自动填充。 确保订阅正确。
  • 函数应用:选择你的函数应用名称。
  • 函数:选择 DpsAdtAllocationFunc

保存详细信息。

Screenshot of the Customs enrollment group details window in the Azure portal.

创建注册后,选择它以查看其设置。 复制注册的主密钥,本文稍后将使用该密钥来配置设备模拟器。

设置设备模拟器

此示例使用设备模拟器,其中包括使用设备预配服务进行预配。 设备模拟器位于你在先决条件部分下载的 Azure 数字孪生和 IoT 中心集成示例中。

上传模型

设备模拟器是一个恒温器类型的设备,使用的模型 ID 为:dtmi:contosocom:DigitalTwins:Thermostat;1。 你需要将此模型上传到 Azure 数字孪生,然后才能为设备创建这种类型的孪生体。

该模型下所示:

{
    "@id": "dtmi:contosocom:DigitalTwins:Thermostat;1",
    "@type": "Interface",
    "@context": "dtmi:dtdl:context;3",
    "contents": [
      {
        "@type": "Property",
        "name": "Temperature",
        "schema": "double"
      }
    ]
  }

若要将此模型上传到孪生实例,请运行以下 Azure CLI 命令,该命令会将上述模型上传为内联 JSON。 可以在浏览器中(使用 Bash 环境)或在计算机上安装 CLI 的情况下,在 Azure Cloud Shell 中运行命令。 实例的主机名有一个占位符(也可以使用实例的易记名称,但性能会略有下降)。

az dt model create --dt-name <instance-hostname-or-name> --models '{  "@id": "dtmi:contosocom:DigitalTwins:Thermostat;1",  "@type": "Interface",  "@context": "dtmi:dtdl:context;2",  "contents": [    {      "@type": "Property",      "name": "Temperature",      "schema": "double"    }  ]}' 

注意

如果你在 Bash 环境中使用 Cloud Shell 以外的任何内容,则可能需要转义内联 JSON 中的某些字符,以便对其进行正确解析。 有关详细信息,请参阅在不同的 shell 中使用特殊字符

有关模型的详细信息,请参阅管理模型

配置和运行模拟器

在本地计算机上的命令窗口中,导航到之前解压缩的示例 Azure 数字孪生并IoT 中心集成,然后导航到设备模拟器目录。 接下来,使用以下命令安装项目的依赖项:

npm install

然后,在设备模拟器目录中,将 .env.template 文件复制到名为 .env 的新文件中,然后收集以下值来填充设置

  • PROVISIONING_IDSCOPE:要获取此值,请在 Azure 门户中导航到你的设备预配服务,然后在菜单选项中选择“概览”,并查找“ID 范围”字段。

    Screenshot of the Azure portal view of the device provisioning overview page highlighting the ID Scope value.

  • PROVISIONING_REGISTRATION_ID:可以为设备选择一个注册 ID。

  • ADT_MODEL_ID:dtmi:contosocom:DigitalTwins:Thermostat;1

  • PROVISIONING_SYMMETRIC_KEY:此环境变量是前面设置的注册的主密钥。 要再次获取此值,请在 Azure 门户中导航到设备预配服务,选择“管理注册”,然后选择之前创建的注册组,再复制“主键”

    Screenshot of the Azure portal view of the device provisioning service manage enrollments page highlighting the SAS primary key value.

现在,使用上述值更新 .env 文件设置

PROVISIONING_HOST = "global.azure-devices-provisioning.net"
PROVISIONING_IDSCOPE = "<Device-Provisioning-Service-Scope-ID>"
PROVISIONING_REGISTRATION_ID = "<Device-Registration-ID>"
ADT_MODEL_ID = "dtmi:contosocom:DigitalTwins:Thermostat;1"
PROVISIONING_SYMMETRIC_KEY = "<Device-Provisioning-Service-enrollment-primary-SAS-key>"

保存并关闭该文件。

开始运行设备模拟器

同样,在命令窗口的“设备模拟器”目录中,使用以下命令启动设备模拟器:

node .\adt_custom_register.js

应该可看到设备注册和连接到 IoT 中心,然后开始发送消息。 Screenshot of the Command window showing device registration and sending messages.

验证

在本文中设置的流会使设备自动注册到 Azure 数字孪生中。 使用以下 Azure 数字孪生 CLI 命令在创建的 Azure 数字孪生实例中查找设备的孪生体。 实例的主机名有一个占位符(也可以使用实例的易记名称,但性能略有下降),每个设备注册 ID 都有一个占位符。

az dt twin show --dt-name <instance-hostname-or-name> --twin-id "<device-registration-ID>"

你应会看到可以在 Azure 数字孪生实例中找到设备的孪生体。 Screenshot of the Command window showing newly created twin.

使用 IoT 中心生命周期事件自动停用设备

在此部分,你要将 IoT 中心生命周期事件附加到 Azure 数字孪生,以便通过以下路径自动停用设备。 此示意图取自前面所示的完整体系结构。

Diagram of the Retire device flow—an excerpt of the solution architecture diagram, following data from a device deletion into Azure Digital Twins.

下面是流程说明:

  1. 外部或手动进程触发在 IoT 中心删除设备的操作。
  2. IoT 中心将删除设备并生成设备生命周期事件,该事件将被路由到事件中心
  3. Azure 函数在 Azure 数字孪生中删除设备的孪生体。

下面部分将逐步讲解设置此自动停用设备流的步骤。

创建事件中心

接下来,需创建一个 Azure 事件中心来接收 IoT 中心生命周期事件。

按照创建事件中心快速入门中所述的步骤执行操作。 将事件中心命名为 lifecycleevents。 在后续部分设置 IoT 中心路由和 Azure 函数时,将使用此事件中心名称。

以下屏幕截图说明了如何创建事件中心。 Screenshot of the Azure portal window showing how to create an event hub with the name lifecycleevents.

为事件中心创建 SAS 策略

接下来,需要创建一个共享访问签名 (SAS) 策略为事件中心配置你的函数应用。 若要创建 SAS 策略,请执行以下操作:

  1. 导航到在 Azure 门户中创建的事件中心,然后在左侧菜单选项中选择“共享访问策略”
  2. 选择添加。 在打开的“添加 SAS 策略”窗口中,输入所选的策略名称,然后选择“侦听”复选框。
  3. 选择创建

Screenshot of the Azure portal showing how to add an event hub SAS policy.

为事件中心配置函数应用

接下来,配置在先决条件部分设置的 Azure 函数应用,以用于你的新事件中心。 你将通过使用事件中心的连接字符串在函数应用中设置一个环境变量来配置函数。

  1. 打开创建的策略,并复制“连接字符串-主密钥”值

    Screenshot of the Azure portal showing how to copy the connection string-primary key.

  2. 使用以下 Azure CLI 命令,将连接字符串添加为函数应用设置中的变量。 此命令可在 Cloud Shell 中运行,如果计算机中已安装 Azure CLI,也可在本地运行。

    az functionapp config appsettings set --settings "EVENTHUB_CONNECTIONSTRING=<Event-Hubs-SAS-connection-string-Listen>" --resource-group <resource-group> --name <your-function-app-name>
    

添加函数以通过 IoT 中心生命周期事件停用

先决条件部分创建的函数应用项目中,创建一个新函数,以使用 IoT 中心生命周期事件停用现有设备。

有关生命周期事件的详细信息,请参阅 IoT 中心非遥测事件。 有关事件中心与 Azure 函数结合使用的详细信息,请参阅适用于 Azure Functions 的 Azure 事件中心触发器

导航到计算机的函数应用项目,然后执行以下步骤。

  1. 首先,在函数应用项目中创建一个事件中心触发器类型的新函数

  2. 向项目中添加一个新 NuGet 包:Microsoft.Azure.Devices.Provisioning.Service。 如果代码中使用的包尚不是项目的一部分,则可能还需要向项目中添加更多包。

  3. 在新创建的函数代码文件中,粘贴以下代码,将该函数重命名为 DeleteDeviceInTwinFunc.cs,然后保存该文件

    // Copyright (c) Microsoft. All rights reserved.
    // Licensed under the MIT license. See LICENSE file in the project root for full license information.
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using Azure;
    using Azure.Core.Pipeline;
    using Azure.DigitalTwins.Core;
    using Azure.Identity;
    using Microsoft.Azure.EventHubs;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Extensions.Logging;
    
    namespace Samples.AdtIothub
    {
        public static class DeleteDeviceInTwinFunc
        {
            private static string adtAppId = "https://digitaltwins.azure.net";
            private static readonly string adtInstanceUrl = Environment.GetEnvironmentVariable("ADT_SERVICE_URL", EnvironmentVariableTarget.Process);
            private static readonly HttpClient singletonHttpClientInstance = new HttpClient();
    
            [FunctionName("DeleteDeviceInTwinFunc")]
            public static async Task Run(
                [EventHubTrigger("lifecycleevents", Connection = "EVENTHUB_CONNECTIONSTRING")] EventData[] events, ILogger log)
            {
                var exceptions = new List<Exception>(events.Length);
    
                // Create Digital Twin client
                var cred = new ManagedIdentityCredential(adtAppId);
                var client = new DigitalTwinsClient(
                    new Uri(adtInstanceUrl),
                    cred,
                    new DigitalTwinsClientOptions
                    {
                        Transport = new HttpClientTransport(singletonHttpClientInstance)
                    });
    
                foreach (EventData eventData in events)
                {
                    try
                    {
                        //log.LogDebug($"EventData: {System.Text.Json.JsonSerializer.Serialize(eventData)}");
    
                        string opType = eventData.Properties["opType"] as string;
                        if (opType == "deleteDeviceIdentity")
                        {
                            string deviceId = eventData.Properties["deviceId"] as string;
    
                            try
                            {
                                // Find twin based on the original Registration ID
                                BasicDigitalTwin digitalTwin = await client.GetDigitalTwinAsync<BasicDigitalTwin>(deviceId);
    
                                // In order to delete the twin, all relationships must first be removed
                                await DeleteAllRelationshipsAsync(client, digitalTwin.Id, log);
    
                                // Delete the twin
                                await client.DeleteDigitalTwinAsync(digitalTwin.Id, digitalTwin.ETag);
                                log.LogInformation($"Twin {digitalTwin.Id} deleted in DT");
                            }
                            catch (RequestFailedException e) when (e.Status == (int)HttpStatusCode.NotFound)
                            {
                                log.LogWarning($"Twin {deviceId} not found in DT");
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        // We need to keep processing the rest of the batch - capture this exception and continue.
                        exceptions.Add(e);
                    }
                }
    
                if (exceptions.Count > 1)
                    throw new AggregateException(exceptions);
    
                if (exceptions.Count == 1)
                    throw exceptions.Single();
            }
    
            /// <summary>
            /// Deletes all outgoing and incoming relationships from a specified digital twin
            /// </summary>
            public static async Task DeleteAllRelationshipsAsync(DigitalTwinsClient client, string dtId, ILogger log)
            {
                AsyncPageable<BasicRelationship> relationships = client.GetRelationshipsAsync<BasicRelationship>(dtId);
                await foreach (BasicRelationship relationship in relationships)
                {
                    await client.DeleteRelationshipAsync(dtId, relationship.Id, relationship.ETag);
                    log.LogInformation($"Twin {dtId} relationship {relationship.Id} deleted in DT");
                }
    
                AsyncPageable<IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync(dtId);
                await foreach (IncomingRelationship incomingRelationship in incomingRelationships)
                {
                    await client.DeleteRelationshipAsync(incomingRelationship.SourceId, incomingRelationship.RelationshipId);
                    log.LogInformation($"Twin {dtId} incoming relationship {incomingRelationship.RelationshipId} from {incomingRelationship.SourceId} deleted in DT");
                }
            }
        }
    }
    
  4. 将包含 DeleteDeviceInTwinFunc.cs 函数的项目发布到 Azure 中的函数应用

    若要了解如何使用 Visual Studio 发布函数,请参阅使用 Visual Studio 开发 Azure Functions。 若要了解如何使用 Visual Studio Code 发布函数,请参阅使用 Visual Studio Code 在 Azure 中创建 C# 函数。 若要了解如何使用 Azure CLI 发布函数,请参阅在 Azure 中从命令行创建 C# 函数

重要

先决条件部分首次创建函数应用时,你可能已经为该函数分配了访问角色,并为其配置了访问 Azure 数字孪生实例所需的应用程序设置。 这些操作需要针对整个函数应用执行一次,因此在继续之前,请验证是否已在你的应用中完成这些操作。 可以在编写应用验证码一文的配置已发布的应用部分找到相关说明。

为生命周期事件创建 IoT 中心路由

现在,设置 IoT 中心路由来路由设备生命周期事件。 在本例中,你将专门侦听以 if (opType == "deleteDeviceIdentity") 标识的设备删除事件。 此事件将触发数字孪生项删除操作,并完成停用设备及其数字孪生的过程。

首先,需要在 IoT 中心创建一个事件中心终结点。 然后,在 IoT 中心添加路由,以便将生命周期事件发送到此事件中心终结点。 按照以下步骤创建事件中心终结点:

  1. Azure 门户中,导航到在先决条件部分创建的 IoT 中心,然后在左侧菜单选项中选择“消息路由”

  2. 选择“自定义终结点”选项卡

  3. 选择“+ 添加”,然后选择“事件中心”以添加事件中心类型的终结点

    Screenshot of the Azure portal showing how to add an Event Hubs custom endpoint.

  4. 在打开的“添加事件中心终结点”窗口中,选择以下值:

    • 终结点名称:选择终结点名称。
    • 事件中心命名空间:从下拉列表中选择事件中心命名空间。
    • 事件中心实例:选择在上一步中创建的事件中心名称。
  5. 选择创建。 使此窗口保持打开状态,以便在下一步中添加路由。

    Screenshot of the Azure portal showing how to add an event hub endpoint.

接下来,添加一个路由以连接到上一步中创建的终结点,其中包含发送删除事件的路由查询。 按照以下步骤创建路由:

  1. 导航到“路由”选项卡,然后选择“添加”以添加路由。

    Screenshot of the Azure portal showing how to add a route to send events.

  2. 在打开的“添加路由”页中,选择以下值:

    • 名称:选择路由的名称。
    • 终结点:从下拉列表中选择之前创建的事件中心终结点。
    • 数据源:选择“设备生命周期事件”
    • 路由查询:输入 opType='deleteDeviceIdentity'。 此查询将设备生命周期事件限制为仅发送删除事件。
  3. 选择“保存”。

    Screenshot of the Azure portal showing how to add a route to send lifecycle events.

完成此流后,即已完成端到端停用设备所需的一切设置。

Validate

要触发停用进程,需要从 IoT 中心手动删除设备。

可以使用 Azure CLI 命令或 Azure 门户从 IoT 中心手动删除设备。 在 Azure 门户中按照以下步骤删除设备:

  1. 导航到 IoT 中心,然后在左侧菜单选项中选择“IoT 设备”
  2. 你将看到一个设备,带有你在本文前半部分所选的设备注册 ID。 还可以选择删除任何其他设备,前提是该设备在 Azure 数字孪生中具有孪生,在这种情况下,在删除设备后可以验证是否已自动删除该孪生。
  3. 选择设备,然后选择“删除”

Screenshot of the Azure portal showing how to delete device twin from the IoT devices.

可能需要几分钟时间,才能看到 Azure 数字孪生中发生变化。

使用以下 Azure 数字孪生 CLI 命令,验证 Azure 数字孪生实例中该设备的孪生体是否被删除。 实例的主机名有一个占位符(也可以使用实例的易记名称,但性能略有下降),每个设备注册 ID 都有一个占位符。

az dt twin show --dt-name <instance-hostname-or-name> --twin-id "<device-registration-ID>"

你应会看到,在 Azure 数字孪生实例中已找不到该设备的孪生体。

Screenshot of the Command window showing that the twin can't be found anymore.

清理资源

如果不再需要本文中创建的资源,请按照以下步骤删除它们。

利用 Azure Cloud Shell 或本地 Azure CLI,可以使用 az group delete 命令删除资源组中的所有 Azure 资源。 此命令会删除资源组、Azure 数字孪生实例、IoT 中心和中心设备注册、事件网格主题和相关订阅、事件中心命名空间和两个 Azure Functions 应用,包括存储等关联资源。

重要

删除资源组的操作不可逆。 资源组以及包含在其中的所有资源将被永久删除。 请确保不会意外删除错误的资源组或资源。

az group delete --name <your-resource-group>

然后,删除从本地计算机下载的项目示例文件夹。

后续步骤

为设备创建的数字孪生将在 Azure 数字孪生中存储为平级层次结构,但可为它们增添模型信息和组织的多级别层次结构。 要了解有关此概念的详细信息,请阅读:

有关通过 Azure 函数使用 HTTP 请求的详细信息,请参阅:

你可以编写自定义逻辑,以使用已存储在 Azure 数字孪生中的模型和图数据自动提供此信息。 若要详细了解如何在孪生图中进行管理、升级和检索信息,请参阅以下操作指南: