Um die Funktion des zuvor angelegten Device Provisioning Service zu testen, werden wir zum einen ein simuliertes IoT-Ger√§t verwenden und anschlie√üend im n√§chsten Artikel auch einen ESP32. Der Code ist einfach aufgebaut aufgebaut und √ľberschaubar. Es ist sehr einfach m√∂glich den Code von oben nach unten zu lesen und zu verstehen was dort passiert. Ich werde die wichtigsten Parameter erkl√§ren die gesetzt werden m√ľssen und wo man diese im Azure Portal erh√§lt. Anschlie√üend werde ich kurz die einzelnen Code-Abschnitte erkl√§ren.

Unser simuliertes IoT-Ger√§t ist eine Microsoft .NET Core Konsolenanwendung in C# geschrieben. Der Code wurde im Original gepostet auf GitHub MicrosoftLearning/AZ-220ZH-Microsoft-Azure-IoT-Developer … /… /06-Automatic Enrollment of Devices in DPS/Final/ContainerDevice/Program.cs.

Wir starten Visual Studio und erstellen ein neues Konsolen-App (.NET Core) Projekt.

Anschlie√üend kopieren wir den Code der Datei Program.cs aus der GitHub Repository in die Program.cs Datei im Projekt. Damit die Paket-Konflikte behoben werden, m√ľssen wir die nachfolgenden Pakete mit Hilfe des NuGet-Paketmanagers installieren.

Nuget-Packages

Nachfolgend die NuGet-Pakete als Install-Package Befehle, so dass diese hier direkt kopiert werden können.

Install-Package Microsoft.Azure.Devices.Client -Version 1.35.0
Install-Package Microsoft.Azure.Devices.Provisioning.Client -Version 1.16.3
Install-Package Microsoft.Azure.Devices.Provisioning.Transport.Mqtt -Version 1.14.0
Install-Package Microsoft.Azure.Devices.Provisioning.Transport.Amqp -Version 1.14.0-preview-001
Install-Package Microsoft.Azure.Devices.Provisioning.Transport.Http -Version 1.13.0-preview-001
Install-Package Newtonsoft.Json -Version 13.0.1-beta2

Danach m√ľssen wir das IoT-Ger√§tezertifkat, was wir im vorherigen Artikel Der Microsoft Azure Device Provisioning Service im letzten Schritt mit dem IoT-Ger√§tenamen f√ľgen wir per drag and drop hinzu. Diese Zertifikatsdatei f√ľgen wir, beispielsweise per Drag and Drop, unserem Projekt hinzu.

Der Code

using Microsoft.Azure.Devices.Client;
using Microsoft.Azure.Devices.Provisioning.Client;
using Microsoft.Azure.Devices.Provisioning.Client.Transport;
using Microsoft.Azure.Devices.Shared;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Security.Cryptography.X509Certificates;
namespace IoTGarage_Azure_05_DPS_x509_DeviceProvisioning
{
    class Program
    {
        // Azure Device Provisioning Service (DPS) ID Scope
        private static string dpsIdScope = "0ne00XXA1XX";
        // Certificate (PFX) File Name
        private static string certificateFileName = "bogxiotdevice1000.pfx";
        // Certificate (PFX) Password
        private static string certificatePassword = "1234";
        // NOTE: For the purposes of this example, the certificatePassword is
        // hard coded. In a production device, the password will need to be stored
        // in a more secure manner. Additionally, the certificate file (PFX) should
        // be stored securely on a production device using a Hardware Security Module.
        private const string GlobalDeviceEndpoint = "global.azure-devices-provisioning.net";
        private static int telemetryDelay = 1;
        private static DeviceClient deviceClient;
        // INSERT Main method below here
        public static async Task Main(string[] args)
        {
            X509Certificate2 certificate = LoadProvisioningCertificate();
            using (var security = new SecurityProviderX509Certificate(certificate))
            using (var transport = new ProvisioningTransportHandlerAmqp(TransportFallbackType.TcpOnly))
            {
                ProvisioningDeviceClient provClient =
                    ProvisioningDeviceClient.Create(GlobalDeviceEndpoint, dpsIdScope, security, transport);
                using (deviceClient = await ProvisionDevice(provClient, security))
                {
                    await deviceClient.OpenAsync().ConfigureAwait(false);
                    // INSERT Setup OnDesiredPropertyChanged Event Handling below here
                    await deviceClient.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertyChanged, null).ConfigureAwait(false);
                    // INSERT Load Device Twin Properties below here
                    var twin = await deviceClient.GetTwinAsync().ConfigureAwait(false);
                    await OnDesiredPropertyChanged(twin.Properties.Desired, null);
                    // Start reading and sending device telemetry
                    Console.WriteLine("Start reading and sending device telemetry...");
                    await SendDeviceToCloudMessagesAsync();
                    await deviceClient.CloseAsync().ConfigureAwait(false);
                }
            }
        }
        // INSERT LoadProvisioningCertificate method below here
        private static X509Certificate2 LoadProvisioningCertificate()
        {
            var certificateCollection = new X509Certificate2Collection();
            certificateCollection.Import(certificateFileName, certificatePassword, X509KeyStorageFlags.UserKeySet);
            X509Certificate2 certificate = null;
            foreach (X509Certificate2 element in certificateCollection)
            {
                Console.WriteLine($"Found certificate: {element?.Thumbprint} {element?.Subject}; PrivateKey: {element?.HasPrivateKey}");
                if (certificate == null && element.HasPrivateKey)
                {
                    certificate = element;
                }
                else
                {
                    element.Dispose();
                }
            }
            if (certificate == null)
            {
                throw new FileNotFoundException($"{certificateFileName} did not contain any certificate with a private key.");
            }
            Console.WriteLine($"Using certificate {certificate.Thumbprint} {certificate.Subject}");
            return certificate;
        }
        // INSERT ProvisionDevice method below here
        private static async Task<DeviceClient> ProvisionDevice(ProvisioningDeviceClient provisioningDeviceClient, SecurityProviderX509Certificate security)
        {
            var result = await provisioningDeviceClient.RegisterAsync().ConfigureAwait(false);
            Console.WriteLine($"ProvisioningClient AssignedHub: {result.AssignedHub}; DeviceID: {result.DeviceId}");
            if (result.Status != ProvisioningRegistrationStatusType.Assigned)
            {
                throw new Exception($"DeviceRegistrationResult.Status is NOT 'Assigned'");
            }
            var auth = new DeviceAuthenticationWithX509Certificate(
                result.DeviceId,
                security.GetAuthenticationCertificate());
            return DeviceClient.Create(result.AssignedHub, auth, TransportType.Amqp);
        }
        private static async Task SendDeviceToCloudMessagesAsync()
        {
            var sensor = new EnvironmentSensor();
            while (true)
            {
                var currentTemperature = sensor.ReadTemperature();
                var currentHumidity = sensor.ReadHumidity();
                var currentPressure = sensor.ReadPressure();
                var currentLocation = sensor.ReadLocation();
                var messageString = CreateMessageString(currentTemperature,
                                                        currentHumidity,
                                                        currentPressure,
                                                        currentLocation);
                var message = new Message(Encoding.ASCII.GetBytes(messageString));
                // Add a custom application property to the message.
                // An IoT hub can filter on these properties without access to the message body.
                message.Properties.Add("temperatureAlert", (currentTemperature > 30) ? "true" : "false");
                // Send the telemetry message
                await deviceClient.SendEventAsync(message);
                Console.WriteLine("{0} > Sending message: {1}", DateTime.Now, messageString);
                // Delay before next Telemetry reading
                await Task.Delay(telemetryDelay * 1000);
            }
        }
        private static string CreateMessageString(double temperature, double humidity, double pressure, EnvironmentSensor.Location location)
        {
            // Create an anonymous object that matches the data structure we wish to send
            var telemetryDataPoint = new
            {
                temperature = temperature,
                humidity = humidity,
                pressure = pressure,
                latitude = location.Latitude,
                longitude = location.Longitude
            };
            var messageString = JsonConvert.SerializeObject(telemetryDataPoint);
            // Create a JSON string from the anonymous object
            return JsonConvert.SerializeObject(telemetryDataPoint);
        }
        // INSERT OnDesiredPropertyChanged method below here
        private static async Task OnDesiredPropertyChanged(TwinCollection desiredProperties, object userContext)
        {
            Console.WriteLine("Desired Twin Property Changed:");
            Console.WriteLine($"{desiredProperties.ToJson()}");
            // Read the desired Twin Properties
            if (desiredProperties.Contains("telemetryDelay"))
            {
                string desiredTelemetryDelay = desiredProperties["telemetryDelay"];
                if (desiredTelemetryDelay != null)
                {
                    telemetryDelay = int.Parse(desiredTelemetryDelay);
                }
                // if desired telemetryDelay is null or unspecified, don't change it
            }
            // Report Twin Properties
            var reportedProperties = new TwinCollection();
            reportedProperties["telemetryDelay"] = telemetryDelay.ToString();
            await deviceClient.UpdateReportedPropertiesAsync(reportedProperties).ConfigureAwait(false);
            Console.WriteLine("Reported Twin Properties:");
            Console.WriteLine($"{reportedProperties.ToJson()}");
        }
    }
    internal class EnvironmentSensor
    {
        // Initial telemetry values
        double minTemperature = 20;
        double minHumidity = 60;
        double minPressure = 1013.25;
        double minLatitude = 39.810492;
        double minLongitude = -98.556061;
        Random rand = new Random();
        internal class Location
        {
            internal double Latitude;
            internal double Longitude;
        }
        internal double ReadTemperature()
        {
            return minTemperature + rand.NextDouble() * 15;
        }
        internal double ReadHumidity()
        {
            return minHumidity + rand.NextDouble() * 20;
        }
        internal double ReadPressure()
        {
            return minPressure + rand.NextDouble() * 12;
        }
        internal Location ReadLocation()
        {
            return new Location { Latitude = minLatitude + rand.NextDouble() * 0.5, Longitude = minLongitude + rand.NextDouble() * 0.5 };
        }
    }
}

Im Code gibt es vier wichtige Konstanten die angepasst werden m√ľssen:

  • dpsIdScope: ID Scope des Device Provisioning Services
  • certificateFilename: Die Zertifikatsdatei
  • certificatePassword: Das Zertifikatspasswort was wir in der PowerShell w√§hrend der Generierung des Zertifikats festgelegt haben
  • GlobalDeviceEndpoint: Der globale Device Endpunkt

Den Azure Device Provisioning Service ID Scope finden wir im Azure Portal. √Ėffnen dazu im Azure Portal unseren Device Provisioning Service iotgdps und in der √úbersicht nehmen wir den Wert ID Scope und weisen diesen Wert der Konstante dpsIdScope zu.

Den Namen der Zertifikatsdatei, in unserem Fall bogxiotdevice1000.pfx, weisen wir der Konstante certificateFileName zu. Das Passwort, dass wir während der Generierung des Zertifikats vergeben haben, weisen wir der Konstante certificatePassword zu.

F√ľr die letzte zu bef√ľllende Konstante GlobalDeviceEndpoint nehmen wir den Wert Global device endpoint aus der √úbersichtsseite des Device Provisioning Service im Azure Portal.

Damit sind fertig. Um zu schauen, ob das was wir erstellt und angepasst haben auch funktioniert, f√ľhren wir die Anwendung aus und sehen, dass unserem Client ein IoT Hub sowie eine Ger√§te-ID zugewiesen wurde. Anschlie√üend beginnt das simulierte Ger√§t mit dem Senden von simulierten Telemetriedaten.

Um zu schauen, ob unser simuliertes Ger√§t auch im IoT Hub registriert wurde, √∂ffnen wir im Azure Portal unseren IoT Hub iotghub. Im linken Men√ľ w√§hlen wir den Punkt Explorers > IoT devices und sollten nun auf der rechten Seite in der Liste der IoT-Ger√§te uns simuliertes Ger√§t mit der ID bogxiotdevice1000 sehen.

In der Enrollment Group im Device Provisioning Service sollte das simulierte Ger√§t auch zu finden sein. Dazu √∂ffnen wir im Azure Portal unseren Device Provisioning Service und klicken im linken Men√ľ auf Settings > Manage enrollments. Rechts im Fenster klicken wir dann auf den Reiter Enrollment Groups und w√§hlen dann in der Liste die Enrollment Group bogxgroup1-rootca.

In der Enrollment Group Details Ansicht klicken wir auf den Reiter Registration Records.

Auch hier erscheint in der Liste unser simuliertes Gerät mit der Id bogxiotdevice1000.

Damit funktioniert unser Device Provisioning Service sowie unsere Zertifikatskette. Im nächsten Artikel werden wir einen ESP32 via Device Provisioning Service registrieren.