教程:通过设备预配服务 (DPS) 使用自定义分配策略

自定义分配策略让你能够更好地控制将设备分配到 IoT 中心的方式。 当 Azure IoT 中心设备预配服务 (DPS) 提供的策略不能满足你的方案的要求时,可使用自定义分配策略定义你自己的分配策略。 自定义分配策略在 Azure Functions中托管的 Webhook 中实现,并在一个或多个单独的注册和/或注册组上配置。 当设备使用配置的注册项向 DPS 注册时,DPS 会调用 Webhook 来找出设备应注册到的 IoT 中心,以及(可选)其初始状态。 若要了解详细信息,请参阅了解自定义分配策略

本教程演示使用 C# 编写的 Azure 函数的自定义分配策略。 设备分配到两个 IoT 中心之一,分别表示 Contoso 烤箱分区和 Contoso 热泵分区。 请求预配的设备必须具有含以下后缀之一的注册 ID 才能被接受进行预配:

  • -contoso-tstrsd-007,适用于 Contoso 烤箱分区
  • -contoso-hpsd-088,适用于 Contoso 热泵分区

使用 Azure IoT C SDK 中包含的预配示例对设备进行模拟。

在本教程中,你将执行以下操作:

  • 使用 Azure CLI 创建一个 DPS 实例,同时创建两个 Contoso 部门 IoT 中心(Contoso 烤箱部门和 Contoso 热泵部门)并将其链接到该实例。
  • 创建一个实现自定义分配策略的 Azure 函数。
  • 使用 Azure 函数为自定义分配策略创建新的注册组。
  • 为两个模拟设备创建设备对称密钥。
  • 为 Azure IoT C SDK 设置开发环境。
  • 模拟设备,并验证是否根据自定义分配策略中的示例代码对其进行预配。

如果没有 Azure 试用版订阅,请在开始前创建 Azure 试用版订阅

先决条件

以下先决条件适用于 Windows 开发环境。 对于 Linux 或 macOS,请参阅 SDK 文档的准备开发环境中的相应部分。

创建预配服务和两个 IoT 中心

在本部分,你将使用 Azure Power Shell 创建一个预配服务和表示“Contoso 烤箱分部”和“Contoso 热泵分部”的两个 IoT 中心。

  1. 首先,在工作区中设置环境变量,简化本教程中的命令。

    DPS 和 IoT 中心名称必须是全局唯一的。 将 SUFFIX 占位符值替换为你自己的值。

    此外,本教程稍后创建的 Azure 函数代码会查找名称中具有 -toasters--heatpumps- 的 IoT 中心。 如果更改建议的值,请确保使用包含所需子字符串的名称。

    #!/bin/bash
    export RESOURCE_GROUP="contoso-us-resource-group"
    export LOCATION="chinanorth"
    export DPS="contoso-provisioning-service-SUFFIX"
    export TOASTER_HUB="contoso-toasters-hub-SUFFIX"
    export HEATPUMP_HUB="contoso-heatpumps-hub-SUFFIX"
    
    # PowerShell
    $env:RESOURCE_GROUP = "contoso-us-resource-group"
    $env:LOCATION = "chinanorth"
    $env:DPS = "contoso-provisioning-service-SUFFIX"
    $env:TOASTER_HUB = "contoso-toasters-hub-SUFFIX"
    $env:HEATPUMP_HUB = "contoso-heatpumps-hub-SUFFIX"
    

    提示

    本教程使用的命令默认在“中国北部”位置创建资源。 我们建议在与你最靠近的区域中创建支持设备预配服务的资源。 若要查看可用位置的列表,可以转到 Azure 状态页,在其中搜索“设备预配服务”。 在命令中,可以使用一个单词或多个单词的格式来指定位置,例如 chinanorth、China North、WEST US 等。该值不区分大小写。

  2. 使用 az group create 命令创建 Azure 资源组。 Azure 资源组是在其中部署和管理 Azure 资源的逻辑容器。

    以下示例创建了一个资源组。 建议对本教程中创建的所有资源使用一个组。 此方法使你能够在完成后更为轻松地进行清理。

    az group create --name $RESOURCE_GROUP --location $LOCATION
    
  3. 使用 az iot dps create 命令创建设备预配服务 (DPS) 的实例。 该预配服务将添加到 contoso-us-resource-group

    az iot dps create --name $DPS --resource-group $RESOURCE_GROUP --location $LOCATION
    

    该命令可能需要几分钟时间才能完成。

  4. 使用 az iot hub create 命令创建“Contoso 烤箱部门”IoT 中心。 IoT 中心将添加到 contoso-us-resource-group

    az iot hub create --name $TOASTER_HUB --resource-group $RESOURCE_GROUP --location $LOCATION --sku S1
    

    该命令可能需要几分钟时间才能完成。

  5. 使用 az iot hub create 命令创建“Contoso 热泵部门”IoT 中心。 此 IoT 中心还会添加到 contoso-us-resource-group

    az iot hub create --name $HEATPUMP_HUB --resource-group $RESOURCE_GROUP --location $LOCATION --sku S1
    

    该命令可能需要几分钟时间才能完成。

  6. 运行下面的两个命令来获取已创建的中心的连接字符串。

    az iot hub connection-string show --hub-name $TOASTER_HUB --key primary --query connectionString -o tsv
    az iot hub connection-string show --hub-name $HEATPUMP_HUB --key primary --query connectionString -o tsv
    
  7. 运行以下命令来将中心链接到 DPS 资源。 将占位符替换为上一步中的中心连接字符串。

    az iot dps linked-hub create --dps-name $DPS --resource-group $RESOURCE_GROUP --location $LOCATION --connection-string <toaster_hub_connection_string>
    az iot dps linked-hub create --dps-name $DPS --resource-group $RESOURCE_GROUP --location $LOCATION --connection-string <heatpump_hub_connection_string>
    

创建自定义分配函数

在本部分,你将创建一个实现自定义分配策略的 Azure 函数。 此函数根据设备的注册 ID 是包含字符串 -contoso-tstrsd-007 还是 -contoso-hpsd-088,来确定要将该设备注册到哪个部门 IoT 中心。 它还根据设备是烤箱还是热泵,来设置设备孪生的初始状态。

  1. 登录 Azure 门户

  2. 在搜索框中,搜索并选择“函数应用”

  3. 选择“创建”或“创建函数应用”

  4. 在“函数应用”创建页上的“基本信息”选项卡下,输入新函数应用的以下设置,然后选择“查看 + 创建”:

    参数
    订阅 确保已选择为本教程创建资源时所在的订阅。
    资源组 选择你在上一部分中创建的资源组。 上一部分中提供的默认值为 contoso-us-resource-group
    函数应用名称 为函数应用提供名称。
    是否要部署代码或容器映像? 代码
    运行时堆栈 .NET
    版本 选择任何一个“进程内模型”版本
    区域 选择附近的区域。

    注意

    默认已启用 Application Insights。 本教程不需要 Application Insights,但它可以帮助你了解和调查处理自定义分配时遇到的任何问题。 如果需要,可以禁用 Application Insights,方法是选择“监视”选项卡,然后对“启用 Application Insights”选择“否”。

    屏幕截图显示 Azure 门户中在“创建函数应用”窗体。

  5. 在“查看 + 创建”选项卡上,选择“创建”以创建函数应用

  6. 部署可能需要几分钟时间。 完成后,选择“转到资源”。

  7. 在函数应用“概述”页的左窗格中,选择“创建函数”

    屏幕截图显示选择在 Azure 门户中创建函数的选项。

  8. 在“创建函数”页上,选择“HTTP 触发器”模板,然后选择“下一步”

  9. 在“模板详细信息”选项卡上,选择“匿名”作为授权级别,然后选择“创建”

    屏幕截图显示将授权级别设置为匿名。

    提示

    如果将授权级别保留为“函数”,则需要使用函数 API 密钥配置 DPS 注册。 有关详细信息,请参阅 Azure Functions HTTP 触发器

  10. 当 HttpTrigger1 函数打开时,选择左窗格中的“代码 + 测试”。 这允许你编辑函数的代码。 run.csx 代码文件应该会打开供编辑。

  11. 引用所需的 NuGet 包。 为了创建初始设备孪生,自定义分配函数将使用必须载入托管环境的两个 NuGet 包中定义的类。 在 Azure Functions 中,NuGet 包是使用 function.proj 文件引用的。 在此步骤中,你将保存并上传所需程序集的 function.proj 文件。 有关详细信息,请参阅通过 Azure Functions 使用 NuGet 包

    1. 将以下行复制到你喜欢使用的编辑器中,并在你的计算机上将该文件保存为“function.proj”。

      <Project Sdk="Microsoft.NET.Sdk">  
          <PropertyGroup>  
              <TargetFramework>netstandard2.0</TargetFramework>  
          </PropertyGroup>  
          <ItemGroup>  
              <PackageReference Include="Microsoft.Azure.Devices.Provisioning.Service" Version="1.18.1" />
              <PackageReference Include="Microsoft.Azure.Devices.Shared" Version="1.30.1" />
          </ItemGroup>  
      </Project>
      
    2. 选择位于代码编辑器上方的“上传”按钮,以上传“function.proj”文件。 上传后,在代码编辑器中使用下拉框选择该文件来验证内容。

    3. 在代码编辑器中选择 function.proj 文件并验证其内容。 如果 function.proj 文件为空,请将上面的行复制到文件中并保存。 (有时上传会在不上传内容的情况下创建文件。)

  12. 请确保在代码编辑器中为 HttpTrigger1 选择“run.csx”。 将 HttpTrigger1 函数的代码替换为以下代码,然后选择“保存”:

    #r "Newtonsoft.Json"
    
    using System.Net;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Primitives;
    using Newtonsoft.Json;
    
    using Microsoft.Azure.Devices.Shared;               // For TwinCollection
    using Microsoft.Azure.Devices.Provisioning.Service; // For TwinState
    
    public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");
    
        // Get request body
        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        dynamic data = JsonConvert.DeserializeObject(requestBody);
    
        log.LogInformation("Request.Body:...");
        log.LogInformation(requestBody);
    
        // Get registration ID of the device
        string regId = data?.deviceRuntimeContext?.registrationId;
    
        string message = "Uncaught error";
        bool fail = false;
        ResponseObj obj = new ResponseObj();
    
        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)
            {
                message = "No hub group defined for the enrollment.";
                log.LogInformation("linkedHubs : NULL");
                fail = true;
            }
            else
            {
                // This is a Contoso Toaster Model 007
                if (regId.Contains("-contoso-tstrsd-007"))
                {
                    //Find the "-toasters-" IoT hub configured on the enrollment
                    foreach(string hubString in hubs)
                    {
                        if (hubString.Contains("-toasters-"))
                            obj.iotHubHostName = hubString;
                    }
    
                    if (obj.iotHubHostName == null)
                    {
                        message = "No toasters hub found for the enrollment.";
                        log.LogInformation(message);
                        fail = true;
                    }
                    else
                    {
                        // Specify the initial tags for the device.
                        TwinCollection tags = new TwinCollection();
                        tags["deviceType"] = "toaster";
    
                        // Specify the initial desired properties for the device.
                        TwinCollection properties = new TwinCollection();
                        properties["state"] = "ready";
                        properties["darknessSetting"] = "medium";
    
                        // Add the initial twin state to the response.
                        TwinState twinState = new TwinState(tags, properties);
                        obj.initialTwin = twinState;
                    }
                }
                // This is a Contoso Heat pump Model 008
                else if (regId.Contains("-contoso-hpsd-088"))
                {
                    //Find the "-heatpumps-" IoT hub configured on the enrollment
                    foreach(string hubString in hubs)
                    {
                        if (hubString.Contains("-heatpumps-"))
                            obj.iotHubHostName = hubString;
                    }
    
                    if (obj.iotHubHostName == null)
                    {
                        message = "No heat pumps hub found for the enrollment.";
                        log.LogInformation(message);
                        fail = true;
                    }
                    else
                    {
                        // Specify the initial tags for the device.
                        TwinCollection tags = new TwinCollection();
                        tags["deviceType"] = "heatpump";
    
                        // Specify the initial desired properties for the device.
                        TwinCollection properties = new TwinCollection();
                        properties["state"] = "on";
                        properties["temperatureSetting"] = "65";
    
                        // Add the initial twin state to the response.
                        TwinState twinState = new TwinState(tags, properties);
                        obj.initialTwin = twinState;
                    }
                }
                // Unrecognized device.
                else
                {
                    fail = true;
                    message = "Unrecognized device registration.";
                    log.LogInformation("Unknown device registration");
                }
            }
        }
    
        log.LogInformation("\nResponse");
        log.LogInformation((obj.iotHubHostName != null) ? JsonConvert.SerializeObject(obj) : message);
    
        return (fail)
            ? new BadRequestObjectResult(message) 
            : (ActionResult)new OkObjectResult(obj);
    }
    
    public class ResponseObj
    {
        public string iotHubHostName {get; set;}
        public TwinState initialTwin {get; set;}
    }
    

创建注册

在本部分,你将创建一个使用自定义分配策略的新注册组。 为简单起见,本教程将在注册中使用对称密钥证明。 对于更安全的解决方案,请考虑使用具有信任链的 X.509 证书证明

  1. 登录到 Azure 门户,并导航到设备预配服务实例。

  2. 从导航菜单的“设置”部分选择“管理注册”。

  3. 选择“添加注册组”。

  4. 在“添加注册组”页的“注册 + 预配”选项卡上,提供以下信息以配置注册组详细信息:

    字段 说明
    证明 选择“对称密钥”作为证明类型
    对称密钥设置 选中“自动生成对称密钥”复选框。
    组名 输入“contoso-custom-allocated-devices”作为组名称。
    预配状态 选中“启用此注册”复选框。
  5. 选择“下一步:IoT 中心”。

  6. 在“添加注册组”页的“IoT 中心”选项卡上,提供以下信息以确定注册组可以预配设备的目标 IoT 中心:

    字段 说明
    目标 IoT 中心 选择一个或多个链接的 IoT 中心,或向 IoT 中心添加新链接。
    分配策略 选择“自定义(使用 Azure 函数)”。 选择“选择 Azure 函数”,然后根据提示选择为本教程创建的函数。
  7. 选择“查看 + 创建”。

  8. 在“审阅 + 创建”选项卡上,验证你的全部值,然后选择“创建”。

保存注册后,重新打开它,并记录“主密钥”。 必须先保存注册,才能生成密钥。 此密钥在下一部分中用于为模拟设备生成唯一的设备密钥。

派生唯一设备密钥

设备不直接使用注册组的主对称密钥。 你可以改为使用主密钥来派生每个设备的设备密钥。 在本部分,你将创建两个唯一的设备密钥。 一个密钥用于模拟的烤箱设备。 另一个密钥用于模拟的热泵设备。

若要派生设备密钥,请使用前面记下的注册组主密钥来计算每个设备的设备注册 ID 的 HMAC-SHA256,并将结果转换为 Base64 格式。 有关使用注册组创建派生设备密钥的详细信息,请参阅对称密钥证明的组注册部分。

对于本教程中的示例,使用以下两个设备注册 ID 并计算这两个设备的设备密钥。 这两个注册 ID 都具有有效的后缀,以与自定义分配策略的示例代码结合使用:

  • breakroom499-contoso-tstrsd-007
  • mainbuilding167-contoso-hpsd-088

Azure CLI 的 IoT 扩展提供了用于生成派生设备密钥的 iot dps enrollment-group compute-device-key 命令。 可在基于 Windows 或 Linux 的系统上通过 PowerShell 或 Bash shell 使用此命令。

--key 参数值替换为注册组中的主密钥。

az iot dps enrollment-group compute-device-key --key <ENROLLMENT_GROUP_KEY> --registration-id breakroom499-contoso-tstrsd-007
az iot dps enrollment-group compute-device-key --key <ENROLLMENT_GROUP_KEY> --registration-id mainbuilding167-contoso-hpsd-088

注意

还可以向 iot dps enrollment-group compute-device-key 命令提供注册组 ID,而不是对称密钥。 例如:

az iot dps enrollment-group compute-device-key -g contoso-us-resource-group --dps-name contoso-provisioning-service-1098 --enrollment-id contoso-custom-allocated-devices --registration-id breakroom499-contoso-tstrsd-007

模拟设备使用含有每个注册 ID 的派生的设备密钥,以执行对称密钥证明。

准备 Azure IoT C SDK 开发环境

在本部分,你将准备一个用于生成 Azure IoT C SDK 的开发环境。 SDK 包含模拟设备的示例代码。 该模拟设备将尝试在设备启动顺序期间进行预配。

本部分面向基于 Windows 的工作站。 有关 Linux 示例,请参阅教程:预配地理延迟中的 VM 设置。

  1. 下载 CMake 生成系统

    在进行 CMake 安装之前,必须在计算机上安装 Visual Studio 必备组件(Visual Studio 和“使用 C++ 的桌面开发”工作负载)。 满足先决条件并验证下载内容后,安装 CMake 生成系统。

  2. 找到最新版 SDK 的标记名称。

  3. 打开命令提示符或 Git Bash shell。 运行以下命令,以克隆最新版本的适用于 C 的 Azure IoT 设备 SDK GitHub 存储库。 将在上一步中找到的标记用作 -b 参数的值,例如:lts_01_2023

    git clone -b <release-tag> https://github.com/Azure/azure-iot-sdk-c.git
    cd azure-iot-sdk-c
    git submodule update --init
    

    应该预料到此操作需要几分钟才能完成。

  4. 在 git 存储库的根目录中创建 cmake 子目录,并导航到该文件夹。 从 azure-iot-sdk-c 目录运行以下命令:

    mkdir cmake
    cd cmake
    
  5. 运行以下命令,生成特定于你的开发客户端平台的 SDK 版本。 将在 cmake 目录中生成模拟设备的 Visual Studio 解决方案。

    cmake -Dhsm_type_symm_key:BOOL=ON -Duse_prov_client:BOOL=ON  ..
    

    如果 cmake 找不到 C++ 编译器,则在运行该命令时可能会出现生成错误。 如果出现这种情况,请尝试在 Visual Studio 命令提示符窗口中运行该命令。

    生成成功后,最后的几个输出行如下所示:

    $ cmake -Dhsm_type_symm_key:BOOL=ON -Duse_prov_client:BOOL=ON  ..
    -- Building for: Visual Studio 15 2017
    -- Selecting Windows SDK version 10.0.16299.0 to target Windows 10.0.17134.
    -- The C compiler identification is MSVC 19.12.25835.0
    -- The CXX compiler identification is MSVC 19.12.25835.0
    
    ...
    
    -- Configuring done
    -- Generating done
    -- Build files have been written to: E:/IoT Testing/azure-iot-sdk-c/cmake
    

模拟设备

在本部分,你将更新前面设置的、位于 Azure IoT C SDK 中的名为“prov_dev_client_sample”的预配示例。

此示例代码模拟将预配请求发送到你的设备预配服务实例的设备启动序列。 启动序列会导致烤箱设备被识别,且使用自定义分配策略将其分配到 IoT 中心。

  1. 在 Azure 门户中,选择设备预配服务的“概述”选项卡,记下“ID 范围”的值 。

    从门户边栏选项卡中提取设备预配服务终结点信息

  2. 在 Visual Studio 中,打开较早前通过运行 CMake 生成的 azure_iot_sdks.sln 解决方案文件。 解决方案文件应位于以下位置:azure-iot-sdk-c\cmake\azure_iot_sdks.sln

  3. 在 Visual Studio 的“解决方案资源管理器”窗口中,导航到“Provision_Samples”文件夹。 展开名为“prov_dev_client_sample”的示例项目。 展开“源文件”,打开“prov_dev_client_sample.c”。

  4. 找到 id_scope 常量,将值替换为前面复制的“ID 范围”值。

    static const char* id_scope = "0ne00002193";
    
  5. 在同一文件中找到 main() 函数的定义。 确保 hsm_type 变量设置为 SECURE_DEVICE_TYPE_SYMMETRIC_KEY,如下所示:

    SECURE_DEVICE_TYPE hsm_type;
    //hsm_type = SECURE_DEVICE_TYPE_TPM;
    //hsm_type = SECURE_DEVICE_TYPE_X509;
    hsm_type = SECURE_DEVICE_TYPE_SYMMETRIC_KEY;
    
  6. main() 函数中,找到对 Prov_Device_Register_Device() 的调用。 在调用之前,添加以下几行代码,这些代码在预配期间使用 Prov_Device_Set_Provisioning_Payload() 传递自定义 JSON 有效负载。 可通过这种方式为自定义分配函数提供详细信息。 也可通过这种方式传递设备类型,而不检查注册 ID。 有关通过 DPS 发送和接收自定义数据有效负载的详细信息,请参阅如何在设备和 DPS 之间传输有效负载

    // An example custom payload
    const char* custom_json_payload = "{\"MyDeviceFirmwareVersion\":\"12.0.2.5\",\"MyDeviceProvisioningVersion\":\"1.0.0.0\"}";
    
    prov_device_result = Prov_Device_Set_Provisioning_Payload(prov_device_handle, custom_json_payload);
    if (prov_device_result != PROV_DEVICE_RESULT_OK)
    {
        (void)printf("\r\nFailure setting provisioning payload: %s\r\n", MU_ENUM_TO_STRING(PROV_DEVICE_RESULT, prov_device_result));
    }
    
  7. 右键单击“prov_dev_client_sample”项目,然后选择“设为启动项目”。

模拟 Contoso 烤箱设备

  1. 若要模拟烤箱设备,请在 prov_dev_client_sample.c 中找到已注释掉的对 prov_dev_set_symmetric_key_info() 的调用。

    // Set the symmetric key if using they auth type
    //prov_dev_set_symmetric_key_info("<symm_registration_id>", "<symmetric_Key>");
    

    取消注释该函数调用,并将占位符值(包括尖括号)替换为烤箱注册 ID 以及前面生成的派生设备密钥。 下面显示的密钥值 JC8F96eayuQwwz+PkE7IzjH2lIAjCUnAa61tDigBnSs= 仅作为示例提供。

    // Set the symmetric key if using they auth type
    prov_dev_set_symmetric_key_info("breakroom499-contoso-tstrsd-007", "JC8F96eayuQwwz+PkE7IzjH2lIAjCUnAa61tDigBnSs=");
    

    保存文件。

  2. 在 Visual Studio 菜单中,选择“调试”>“开始执行(不调试)”以运行该解决方案。 出现重新生成项目的提示时,请选择“是”,以便在运行项目之前重新生成项目 。

    以下输出是模拟烤箱设备成功启动并连接到预配服务实例以通过自定义分配策略分配到烤箱 IoT 中心的一个示例:

    Provisioning API Version: 1.8.0
    
    Registering Device
    
    Provisioning Status: PROV_DEVICE_REG_STATUS_CONNECTED
    Provisioning Status: PROV_DEVICE_REG_STATUS_ASSIGNING
    Provisioning Status: PROV_DEVICE_REG_STATUS_ASSIGNING
    
    Registration Information received from service: contoso-toasters-hub-1098.azure-devices.cn, deviceId: breakroom499-contoso-tstrsd-007
    
    Press enter key to exit:
    

    以下输出是为烤箱设备运行的自定义分配函数代码的示例日志记录输出。 请注意,已成功为烤箱设备选择了一个中心。 还要注意 payload 属性,它包含你添加到代码中的自定义 JSON 内容。 这可供你的代码在 deviceRuntimeContext 中使用。

    在门户中的函数代码下单击“日志”可以获得此日志记录:

    2022-08-03T20:34:41.178 [Information] Executing 'Functions.HttpTrigger1' (Reason='This function was programmatically called via the host APIs.', Id=12950752-6d75-4f41-844b-c253a6653d4f)
    2022-08-03T20:34:41.340 [Information] C# HTTP trigger function processed a request.
    2022-08-03T20:34:41.341 [Information] Request.Body:...
    2022-08-03T20:34:41.341 [Information] {"enrollmentGroup":{"enrollmentGroupId":"contoso-custom-allocated-devices","attestation":{"type":"symmetricKey"},"capabilities":{"iotEdge":false},"etag":"\"0000f176-0000-0700-0000-62eaad1e0000\"","provisioningStatus":"enabled","reprovisionPolicy":{"updateHubAssignment":true,"migrateDeviceData":true},"createdDateTimeUtc":"2022-08-03T17:15:10.8464255Z","lastUpdatedDateTimeUtc":"2022-08-03T17:15:10.8464255Z","allocationPolicy":"custom","iotHubs":["contoso-toasters-hub-1098.azure-devices.cn","contoso-heatpumps-hub-1098.azure-devices.cn"],"customAllocationDefinition":{"webhookUrl":"https://contoso-function-app-1098.chinacloudsites.cn/api/HttpTrigger1?****","apiVersion":"2021-10-01"}},"deviceRuntimeContext":{"registrationId":"breakroom499-contoso-tstrsd-007","currentIotHubHostName":"contoso-toasters-hub-1098.azure-devices.cn","currentDeviceId":"breakroom499-contoso-tstrsd-007","symmetricKey":{},"payload":{"MyDeviceFirmwareVersion":"12.0.2.5","MyDeviceProvisioningVersion":"1.0.0.0"}},"linkedHubs":["contoso-toasters-hub-1098.azure-devices.cn","contoso-heatpumps-hub-1098.azure-devices.cn"]}
    2022-08-03T20:34:41.382 [Information] Response
    2022-08-03T20:34:41.398 [Information] {"iotHubHostName":"contoso-toasters-hub-1098.azure-devices.cn","initialTwin":{"properties":{"desired":{"state":"ready","darknessSetting":"medium"}},"tags":{"deviceType":"toaster"}}}
    2022-08-03T20:34:41.399 [Information] Executed 'Functions.HttpTrigger1' (Succeeded, Id=12950752-6d75-4f41-844b-c253a6653d4f, Duration=227ms)
    

模拟 Contoso 热泵设备

  1. 若要模拟热泵设备,请使用热泵注册 ID 和之前生成的派生设备密钥更新 prov_dev_client_sample.c 中的 prov_dev_set_symmetric_key_info() 调用。 下面显示的密钥值 6uejA9PfkQgmYylj8Zerp3kcbeVrGZ172YLa7VSnJzg= 也仅作为示例提供。

    // Set the symmetric key if using they auth type
    prov_dev_set_symmetric_key_info("mainbuilding167-contoso-hpsd-088", "6uejA9PfkQgmYylj8Zerp3kcbeVrGZ172YLa7VSnJzg=");
    

    保存文件。

  2. 在 Visual Studio 菜单中,选择“调试”>“开始执行(不调试)”以运行该解决方案。 出现重新生成项目的提示时,请选择“是”,以便在运行项目之前重新生成项目 。

    以下输出是模拟热泵设备成功启动并连接到预配服务实例以通过自定义分配策略分配到 Contoso 热泵 IoT 中心的一个示例:

    Provisioning API Version: 1.8.0
    
    Registering Device
    
    Provisioning Status: PROV_DEVICE_REG_STATUS_CONNECTED
    Provisioning Status: PROV_DEVICE_REG_STATUS_ASSIGNING
    Provisioning Status: PROV_DEVICE_REG_STATUS_ASSIGNING
    
    Registration Information received from service: contoso-heatpumps-hub-1098.azure-devices.cn, deviceId: mainbuilding167-contoso-hpsd-088
    
    Press enter key to exit:
    

对自定义分配策略进行故障排除

下表显示了预期场景以及可能遇到的结果错误代码。 使用此表来帮助你解决使用 Azure 函数时自定义分配策略失败的问题。

方案 预配服务的注册结果 预配 SDK 结果
Webhook 返回“200 正常”,其中“iotHubHostName”设置为有效的 IoT 中心主机名 结果状态:已分配 SDK 返回 PROV_DEVICE_RESULT_OK 和中心信息
Webhook 返回“200 正常”,响应中存在“iotHubHostName”,但设置为空字符串或 null 结果状态:已失败

错误代码:CustomAllocationIotHubNotSpecified (400208)
SDK 返回 PROV_DEVICE_RESULT_HUB_NOT_SPECIFIED
Webhook 返回 401 未授权 结果状态:已失败

错误代码:CustomAllocationUnauthorizedAccess (400209)
SDK 返回 PROV_DEVICE_RESULT_UNAUTHORIZED
创建了个人注册,以禁用设备 结果状态:已禁用 SDK 返回 PROV_DEVICE_RESULT_DISABLED
Webhook 返回错误代码 >= 429 DPS 的业务流程将会多次重试。 重试策略当前:

  - 重试计数:10 个
  - 初始间隔:1 秒
  - 增量:9 秒
SDK 将忽略错误并在指定时间提交另一个获取状态消息
Webhook 返回任何其他状态代码 结果状态:已失败

错误代码:CustomAllocationFailed (400207)
SDK 返回 PROV_DEVICE_RESULT_DEV_AUTH_ERROR

清理资源

如果打算继续使用本教程中创建的资源,则可以保留它们。 如果你不打算继续使用这些资源,请使用以下步骤删除本教程创建的所有资源,以避免不必要的费用。

此处的步骤假定你按照名为 contoso-us-resource-group 的同一资源组的指示创建了本教程中的所有资源。

重要

删除资源组的操作不可逆。 资源组以及包含在其中的所有资源将被永久删除。 请确保不要意外删除错误的资源组或资源。 如果在现有的包含要保留资源的资源组中创建了 IoT 中心,则只删除 IoT 中心资源本身,而不要删除资源组。

若要按名称删除资源组:

  1. 登录到 Azure 门户,然后选择“资源组”。

  2. 在“按名称筛选...”文本框中,键入包含资源的资源组名称“contoso-us-resource-group”

  3. 在结果列表中的资源组右侧,选择“...”,然后选择“删除资源组” 。

  4. 系统会要求确认是否删除该资源组。 再次键入资源组的名称进行确认,然后选择“删除” 。 片刻之后,将会删除该资源组及其包含的所有资源。

后续步骤

若要了解有关自定义分配策略的详细信息,请参阅