Skip to main content

使用DPS enrollment Group注册大量的设备

分类:  Azure物联网 标签:  #Azure #Azure IoT Device Provisioning Service # 发布于: 2023-06-14 20:24:55

我们上一篇文章学习了如何使用SAS Key进行单个设备的登记注册,我们本篇使用enrollment group来注册大量的设备。

在操作之前,请参考文章:<>, 创建Azure IoT Hub服务和DPS服务,并将Azure IoT Hub连接到DPS服务中。

创建好了资源之后,并且将iot hub连接到dps之后,我们开始使用Azure cli来创建单个设备登记,并设定使用SAS Key来登记设备。

请使用如下的命令创建分组设备登记:

az iot dps enrollment-group create -g my-sample-resource-group --dps-name my-sample-dps --enrollment-id my-first-enrollment-group

取回DPS服务的idScope:

az iot dps show --name my-sample-dps -g my-sample-resource-group  | jq .properties.idScope

请注意这里需要安装工具jq, 请参考网站:https://stedolan.github.io/jq/

使用如下的取回SAS Key

az iot dps enrollment-group show --dps-name my-sample-dps -g my-sample-resource-group --enrollment-id my-first-enrollment-group --show-key

如下图:


请保存好我们如图所示的primary key。

重要提示
在分组登记中,如果使用SAS Key来注册设备,那么需要注意设备的Key该如何计算:设备的key是通过取回分组的key,然后使用算法HMACSHA256来Hash得到设备的key,
如下所示:

         //首先需要将key 解密
         string originalPrimaryKey = _parameters.PrimaryKey!;
         byte[] decodeKey = Convert.FromBase64String(originalPrimaryKey);


         using HMACSHA256 hmac = new HMACSHA256(decodeKey);
         var deviceId = devicePrefix + "sn" + (i + 1);

         byte[] device_sign1 = hmac.ComputeHash(Encoding.ASCII.GetBytes(deviceId));

         string deviceKey = Convert.ToBase64String(device_sign1);


         Console.WriteLine($"Device id is {deviceId}");
         Console.WriteLine($"Device Key is {deviceKey}");

         using var security = new SecurityProviderSymmetricKey(
             deviceId,
             deviceKey,
             null);

创建应用

请注意我们使用.Net 6创建的代码,请使用下图所示的命令来创建我们的应用。

dotnet new console -o SymmetricKeyGroupSample
cd SymmetricKeyGroupSample
dotnet add package CommandLineParser
dotnet add package Microsoft.Azure.Devices.Client
dotnet add package Microsoft.Azure.Devices.Provisioning.Client
dotnet add package Microsoft.Azure.Devices.Provisioning.Transport.Amqp
dotnet add package Microsoft.Azure.Devices.Provisioning.Transport.Mqtt
dotnet add package Microsoft.Azure.Devices.Provisioning.Transport.Http
code .  //请安装 VS Code

使用Vs Code打开项目之后,在根目录下添加文件GlobalUsing.cs, 添加如下的内容:

global using Microsoft.Azure.Devices.Client;
global using Microsoft.Azure.Devices.Provisioning.Client.Transport;
global using Microsoft.Azure.Devices.Provisioning.Client;
global using Microsoft.Azure.Devices.Shared;
global using System.Text;
global using CommandLine;
global using System.Security.Cryptography;
global using SymmetricKeyGroupSample.Parameters;
global using SymmetricKeyGroupSample.Samples;

作为全局包引用。

需要注意的是本实例使用包CommandLineParser来解析命令行,因此我们需要应一个辅助类来获取命令行的参数。

在项目中创建一个文件:EnrollmentType.cs, 用于定义登记的类型:

namespace SymmetricKeyGroupSample.Samples;

public enum EnrollmentType
{
    /// <summary>
    ///  Enrollment for a single device.
    /// </summary>
    Individual,

    /// <summary>
    /// Enrollment for a group of devices.
    /// </summary>
    Group,
}

创建文件:Parameters.cs, 该文件主要用于获取命令行参数

namespace SymmetricKeyGroupSample.Parameters;

public class OptionParameters
{
    [Option(
        's',
        "IdScope",
        Required = true,
        HelpText = "The Id Scope of the DPS instance")]
    public string? IdScope { get; set; }

    [Option(
        'i',
        "Id",
        Required = true,
        HelpText = "The registration Id when using individual enrollment, or the desired device Id when using group enrollment.")]
    public string? Id { get; set; }

    [Option(
        'p',
        "PrimaryKey",
        Required = true,
        HelpText = "The primary key of the individual enrollment or the derived primary key of the group enrollment. See the ComputeDerivedSymmetricKeyGroupSample for how to generate the derived key.")]
    public string? PrimaryKey { get; set; }

    [Option(
        'f',
        "DevicePrefix",
        Required = true,
        HelpText = "The Device Prefix for all Device"
    )]
    public string? DevicePrefix {get; set;}

    [Option(
        'n',
        "number",
        Required = true,
        HelpText = "How Many device you want to auto-matic generate"
    )]
    public int number {get; set;}

    [Option(
        'e',
        "EnrollmentType",
        Default = EnrollmentType.Group,
        HelpText = "The type of enrollment: Individual or Group")]
    public EnrollmentType EnrollmentType { get; set; }

    [Option(
        'g',
        "GlobalDeviceEndpoint",
        Default = "global.azure-devices-provisioning.cn",
        HelpText = "The global endpoint for devices to connect to.")]
    public string? GlobalDeviceEndpoint { get; set; }

    [Option(
        't',
        "TransportType",
        Default = TransportType.Mqtt,
        HelpText = "The transport to use to communicate with the device provisioning instance. Possible values include Mqtt, Mqtt_WebSocket_Only, Mqtt_Tcp_Only, Amqp, Amqp_WebSocket_Only, Amqp_Tcp_only, and Http1.")]
    public TransportType TransportType { get; set; }
}

请参考上述的类定义,我们可以使用如下的参数说明:

D:\MyProjects\azure-demo\IoT\SymmetricKeyGroupSample>dotnet run

有可用的工作负载更新。有关详细信息,请运行“dotnet 工作负载列表”。
SymmetricKeyGroupSample 1.0.0
Copyright (C) 2021 SymmetricKeyGroupSample

ERROR(S):
  Required option 's, IdScope' is missing.
  Required option 'i, Id' is missing.
  Required option 'p, PrimaryKey' is missing.
  Required option 'f, DevicePrefix' is missing.
  Required option 'n, number' is missing.

  -s, --IdScope                 Required. The Id Scope of the DPS instance

  -i, --Id                      Required. The registration Id when using individual enrollment, or the desired device Id                                when using group enrollment.

  -p, --PrimaryKey              Required. The primary key of the individual enrollment or the derived primary key of the                                group enrollment. See the ComputeDerivedSymmetricKeyGroupSample for how to generate the
                                derived key.

  -f, --DevicePrefix            Required. The Device Prefix for all Device

  -n, --number                  Required. How Many device you want to auto-matic generate

  -e, --EnrollmentType          (Default: Group) The type of enrollment: Individual or Group

  -g, --GlobalDeviceEndpoint    (Default: global.azure-devices-provisioning.cn) The global endpoint for devices to
                                connect to.

  -t, --TransportType           (Default: Mqtt) The transport to use to communicate with the device provisioning
                                instance. Possible values include Mqtt, Mqtt_WebSocket_Only, Mqtt_Tcp_Only, Amqp,
                                Amqp_WebSocket_Only, Amqp_Tcp_only, and Http1.

  --help                        Display this help screen.

  --version                     Display version information.


D:\MyProjects\azure-demo\IoT\SymmetricKeyGroupSample>

在项目目录下创建文件:ProvisioningDeviceClientSample.cs, 该文件内容如下:

namespace SymmetricKeyGroupSample.Samples;

public class ProvisioningDeviceClientSample
{
    private readonly OptionParameters _parameters;

    public ProvisioningDeviceClientSample(OptionParameters parameters)
    {
        _parameters = parameters;
    }

    public async Task RunSampleAsync()
    {
        Console.WriteLine($"初始化Azure IoT Hub DPS客户端...");

        string devicePrefix = _parameters.DevicePrefix!;
        int deviceNumber = _parameters.number;

        for (int i = 0; i <= deviceNumber; i++)
        {
            //首先需要将key 解密
            string originalPrimaryKey = _parameters.PrimaryKey!;
            byte[] decodeKey = Convert.FromBase64String(originalPrimaryKey);


            using HMACSHA256 hmac = new HMACSHA256(decodeKey);
            var deviceId = devicePrefix + "sn" + (i + 1);

            byte[] device_sign1 = hmac.ComputeHash(Encoding.ASCII.GetBytes(deviceId));

            string deviceKey = Convert.ToBase64String(device_sign1);


            Console.WriteLine($"Device id is {deviceId}");
            Console.WriteLine($"Device Key is {deviceKey}");

            using var security = new SecurityProviderSymmetricKey(
                deviceId,
                deviceKey,
                null);

            using var transportHandler = GetTransportHandler();

            ProvisioningDeviceClient provClient = ProvisioningDeviceClient.Create(
                _parameters.GlobalDeviceEndpoint,
                _parameters.IdScope,
                security,
                transportHandler);

            Console.WriteLine($"初始化DPS登记: 注册ID为: {security.GetRegistrationID()}.");
            Console.WriteLine($"当前登记第{i + 1}个设备...");

            Console.WriteLine("通过DPS服务进行注册...");
            DeviceRegistrationResult result = await provClient.RegisterAsync();

            Console.WriteLine($"注册状态: {result.Status}.");
            if (result.Status != ProvisioningRegistrationStatusType.Assigned)
            {
                Console.WriteLine($"未能分配合适的Azure IoT Hub.");
                return;
            }

            Console.WriteLine($"设备ID: {result.DeviceId} 成功注册到IoT Hub: {result.AssignedHub}.");

            Console.WriteLine("创建IoT Hub的SAS Key认证...");
            IAuthenticationMethod auth = new DeviceAuthenticationWithRegistrySymmetricKey(
                result.DeviceId,
                security.GetPrimaryKey());

            Console.WriteLine($"测试连接Azure IoT Hub...");
            using DeviceClient iotClient = DeviceClient.Create(result.AssignedHub, auth, _parameters.TransportType);

            Console.WriteLine("发送测试消息...");
            using var message = new Message(Encoding.UTF8.GetBytes("TestMessage"));
            await iotClient.SendEventAsync(message);

            Console.WriteLine("完成第{i+1}个设备注册和测试.");
        }
    }


    private ProvisioningTransportHandler GetTransportHandler()
    {
        return _parameters.TransportType switch
        {
            TransportType.Mqtt => new ProvisioningTransportHandlerMqtt(),
            TransportType.Mqtt_Tcp_Only => new ProvisioningTransportHandlerMqtt(TransportFallbackType.TcpOnly),
            TransportType.Mqtt_WebSocket_Only => new ProvisioningTransportHandlerMqtt(TransportFallbackType.WebSocketOnly),
            TransportType.Amqp => new ProvisioningTransportHandlerAmqp(),
            TransportType.Amqp_Tcp_Only => new ProvisioningTransportHandlerAmqp(TransportFallbackType.TcpOnly),
            TransportType.Amqp_WebSocket_Only => new ProvisioningTransportHandlerAmqp(TransportFallbackType.WebSocketOnly),
            TransportType.Http1 => new ProvisioningTransportHandlerHttp(),
            _ => throw new NotSupportedException($"不支持的网络协议类型 {_parameters.TransportType}"),
        };
    }
}

最后更改Program.cs文件内容如下:

OptionParameters parameters = null!;
ParserResult<OptionParameters> result = Parser.Default.ParseArguments<OptionParameters>(args)
    .WithParsed(parsedParams =>
    {
        parameters = parsedParams;
    })
    .WithNotParsed(errors =>
    {
        Environment.Exit(1);
    });

var sample = new ProvisioningDeviceClientSample(parameters);
await sample.RunSampleAsync();

return 0;

至此该实例的所有代码都准备好了,您可以通过如下的命令来运行该实例:

dotnet run --s <id-scope> --i <registration-id> --p <primarykey> --f my-enrollment-group-device-demo --n 3

注意这里需要的信息文章前面都已经有了,请在这里替换就好了。

运行结果如下:


检查Azure IoT Hub如下:


最后不要忘记了清理资源,使用如下的命令直接删除资源组:

az group delete --name my-sample-resource-group