A kávégép-eszközszimulátor definiálása

Befejeződött

Az eszközfejlesztőnek gondoskodnia kell arról, hogy az eszköz implementálja az eszközmodellben meghatározott viselkedéseket, hogy az Azure IoT Central figyelhesse és felügyelhesse az eszközt.

Az IoT-kompatibilis kávégép-példában szimulálni fog egy eszközt, amely ellenőrzi a forgatókönyvet a fizikai eszköz csatlakoztatása előtt.

Itt láthatja, hogyan használja egy eszközfejlesztő a CoffeeMaker.jsonban definiált eszközmodellt egy olyan C#-alkalmazás írásához, amely egy kávégépet szimulál.

Az IoT Plug and Playhez kapcsolódó konvenciók

Az IoT Centralhoz csatlakozó eszközöknek követnie kell az IoT Plug and Play konvenciók készletét. Az egyik ilyen konvenció az, hogy az eszköznek a csatlakozáskor el kell küldenie a digitális ikermodell-azonosítót (DTMI ). A DTMI lehetővé teszi, hogy az IoT Central-alkalmazás a megfelelő eszközsablonhoz rendelje az eszközt.

Az Azure IoT-eszköz SDK-k támogatják az IoT Plug and Play konvencióinak támogatását.

A kód áttekintése

Az Azure IoT Central Dokumentációs GitHub-adattár mintakódja tartalmazza a mintakódot. A CoffeeMaker.cs, a Program.cs és a Parameters.cs fájlokat megnyitva megtekintheti a teljes kódot.

A Program.cs metódus a Main SetupDeviceClientAsync metódust a következőre hívja:

  • Az eszköz kiépítése és a dtmi:com:example:ConnectedCoffeeMaker;1 modellazonosító elküldése hasznos adatként.
  • Hozzon létre egy eszközügyfél-példányt az IoT Centralhoz való csatlakozáshoz.
private static async Task<DeviceClient> SetupDeviceClientAsync(Parameters parameters, CancellationToken cancellationToken)
{
    // Provision a device via DPS, by sending the PnP model Id as DPS payload.
    using SecurityProvider symmetricKeyProvider = new SecurityProviderSymmetricKey(parameters.DeviceId, parameters.DevicePrimaryKey, null);
    using ProvisioningTransportHandler mqttTransportHandler = new ProvisioningTransportHandlerMqtt();
    ProvisioningDeviceClient pdc = ProvisioningDeviceClient.Create(DpsEndpoint, parameters.IdScope,
        symmetricKeyProvider, mqttTransportHandler);

    var pnpPayload = new ProvisioningRegistrationAdditionalData
    {
        JsonData = $"{{ \"modelId\": \"{ModelId}\" }}",
    };

    DeviceRegistrationResult dpsRegistrationResult = await pdc.RegisterAsync(pnpPayload, cancellationToken);

    // Initialize the device client instance using symmetric key based authentication, over Mqtt protocol (TCP, with fallback over Websocket) and setting the ModelId into ClientOptions.
    DeviceClient deviceClient;

    var authMethod = new DeviceAuthenticationWithRegistrySymmetricKey(dpsRegistrationResult.DeviceId, parameters.DevicePrimaryKey);

    var options = new ClientOptions
    {
        ModelId = ModelId,
    };

    deviceClient = DeviceClient.Create(dpsRegistrationResult.AssignedHub, authMethod, TransportType.Mqtt, options);
    return deviceClient;
}

A fő metódus ezután létrehoz egy CoffeeMaker-példányt, és meghívja a PerformOperationsAsync metódust az IoT Centrallal folytatott interakciók kezelésére.

A CoffeeMaker.cs-ben a PerformOperationsAsync módszer:

  • Beállítja a kezelők fogadását SetMaintenanceMode és StartBrewing a visszahívások parancsát.
  • Beállítja a kezelőt az ikereszköz kívánt tulajdonságváltozásának kezelésére OptimalTemperature .
  • DeviceWarrantyExpired Frissítések ikereszköz jelentett tulajdonsága az első indításkor.
  • Elindít egy ciklust a hőmérséklet- és páratartalom-telemetriai adatok küldéséhez, függetlenül attól, hogy éppen főzik-e, és ha egy csésze 1 másodpercenként észlelhető.
public async Task PerformOperationsAsync(CancellationToken cancellationToken)
{
    Console.WriteLine($"Device successfully connected to Azure IoT Central");
    
    Console.WriteLine($"- Set handler for \"SetMaintenanceMode\" command.");
    await _deviceClient.SetMethodHandlerAsync("SetMaintenanceMode", HandleMaintenanceModeCommand, _deviceClient, cancellationToken);

    Console.WriteLine($"- Set handler for \"StartBrewing\" command.");
    await _deviceClient.SetMethodHandlerAsync("StartBrewing", HandleStartBrewingCommand, _deviceClient, cancellationToken);

    Console.WriteLine($"- Set handler to receive \"OptimalTemperature\" updates.");
    await _deviceClient.SetDesiredPropertyUpdateCallbackAsync(OptimalTemperatureUpdateCallbackAsync, _deviceClient, cancellationToken);

    Console.WriteLine("- Update \"DeviceWarrantyExpired\" reported property on the initial startup.");
    await UpdateDeviceWarranty(cancellationToken);

    while (!cancellationToken.IsCancellationRequested)
    {
        await SendTelemetryAsync(cancellationToken);
        await Task.Delay(1000, cancellationToken);
    }
}

Telemetria

A SendTelemetryAsync metódus az eszközmodell által megadott formátumban küld telemetriát.

//Send temperature and humidity telemetry, whether it's currently brewing and when a cup is detected.
private async Task SendTelemetryAsync(CancellationToken cancellationToken)
{
    //Simulate the telemetry values
    double temperature = _optimalTemperature + (_random.NextDouble() * 4) - 2;
    double humidity = 20 + (_random.NextDouble() * 80);

    // Cup timer - every 20 seconds randomly decide if the cup is present or not
    if (_cupTimer > 0)
    {
        _cupTimer--;

        if(_cupTimer == 0)
        {
            _cupState = _random.NextDouble()  > 0.5 ? "detected" : "notdetected";
            _cupTimer = 20;
        }
    }

    // Brewing timer
    if (_brewingTimer > 0)
    {
        _brewingTimer--;

        //Finished brewing
        if (_brewingTimer == 0)
        {
            _brewingState = "notbrewing";
        }
    }

    // Create JSON message
    string messageBody = JsonConvert.SerializeObject(
        new
        {
            WaterTemperature = temperature,
            AirHumidity = humidity,
            CupDetected = _cupState,
            Brewing = _brewingState
        });
    using var message = new Message(Encoding.ASCII.GetBytes(messageBody))
    {
        ContentType = "application/json",
        ContentEncoding = "utf-8",
    };

    //Show the information in console
    double infoTemperature = Math.Round(temperature, 1);
    double infoHumidity = Math.Round(humidity, 1);
    string infoCup = _cupState == "detected" ? "Y" : "N";
    string infoBrewing = _brewingState == "brewing" ? "Y" : "N";
    string infoMaintenance = _maintenanceState ? "Y" : "N";

    Console.WriteLine($"Telemetry send: Temperature: {infoTemperature}ºC Humidity: {infoHumidity}% " +
        $"Cup Detected: {infoCup} Brewing: {infoBrewing} Maintenance Mode: {infoMaintenance}");

    //Send the message
    await _deviceClient.SendEventAsync(message, cancellationToken);
}

Properties

A modell megadja a tulajdonságneveket és az adattípusokat, hogy az ikereszközön keresztül szinkronizálja a tulajdonságértékeket az eszköz és az IoT Central között. A metódus UpdateDeviceWarranty elküldi az DeviceWarrantyExpired eszköz állapotát, és a metódus OptimalTemperatureUpdateCallbackAsync kezeli OptimalTemperature az IoT Centralból érkező változásokat.

private async Task OptimalTemperatureUpdateCallbackAsync(TwinCollection desiredProperties, object userContext)
{
    const string propertyName = "OptimalTemperature";

    (bool optimalTempUpdateReceived, double optimalTemp) = GetPropertyFromTwin<double>(desiredProperties, propertyName);
    if (optimalTempUpdateReceived)
    {
        Console.WriteLine($" * Property: Received - {{ \"{propertyName}\": {optimalTemp}°C }}.");

        //Update reported property to In Progress
        string jsonPropertyPending = $"{{ \"{propertyName}\": {{ \"value\": {optimalTemp}, \"ac\": {(int)StatusCode.InProgress}, " +
            $"\"av\": {desiredProperties.Version}, \"ad\": \"In progress - reporting optimal temperature\" }} }}";
        var reportedPropertyPending = new TwinCollection(jsonPropertyPending);
        await _deviceClient.UpdateReportedPropertiesAsync(reportedPropertyPending);
        Console.WriteLine($" * Property: Update - {{\"{propertyName} \": {optimalTemp}°C }} is {StatusCode.InProgress}.");

        //Update the optimal temperature
        _optimalTemperature = optimalTemp;

        //Update reported property to Completed
        string jsonProperty = $"{{ \"{propertyName}\": {{ \"value\": {optimalTemp}, \"ac\": {(int)StatusCode.Completed}, " +
            $"\"av\": {desiredProperties.Version}, \"ad\": \"Successfully updated optimal temperature\" }} }}";
        var reportedProperty = new TwinCollection(jsonProperty);
        await _deviceClient.UpdateReportedPropertiesAsync(reportedProperty);
        Console.WriteLine($" * Property: Update - {{\"{propertyName} \": {optimalTemp}°C }} is {StatusCode.Completed}.");
    }
    else
    {
        Console.WriteLine($" * Property: Received an unrecognized property update from service:\n{desiredProperties.ToJson()}");
    }
}

private async Task UpdateDeviceWarranty(CancellationToken cancellationToken)
{
    const string propertyName = "DeviceWarrantyExpired";

    var reportedProperties = new TwinCollection();
    reportedProperties[propertyName] = _warrantyState;

    await _deviceClient.UpdateReportedPropertiesAsync(reportedProperties, cancellationToken);
    Console.WriteLine($" * Property: Update - {{ \"{propertyName}\": {_warrantyState} }} is {StatusCode.Completed}.");
}

Parancsok

A modell megadja az eszköz által használandó parancsneveket és paramétereket. Az IoT Centraltól küldött parancsok metódusai HandleMaintenanceModeCommand és HandleStartBrewingCommand kezelése.

// The callback to handle "SetMaintenanceMode" command.
private Task<MethodResponse> HandleMaintenanceModeCommand(MethodRequest request, object userContext)
{
    try
    {
        Console.WriteLine(" * Maintenance command received");

        if (_maintenanceState)
        {
            Console.WriteLine(" - Warning: The device is already in the maintenance mode.");
        }

        //Set state
        _maintenanceState = true;

        //Send response
        byte[] responsePayload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject("Success"));
        return Task.FromResult(new MethodResponse(responsePayload, (int)StatusCode.Completed));
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Exception while handling \"SetMaintenanceMode\" command: {ex}");
        return Task.FromResult(new MethodResponse((int)StatusCode.BadRequest));
    }

}

// The callback to handle "StartBrewing" command.
private Task<MethodResponse> HandleStartBrewingCommand(MethodRequest request, object userContext)
{
    try
    {
        Console.WriteLine(" * Start brewing command received");

        if (_brewingState == "brewing")
        {
            Console.WriteLine(" - Warning: The device is already brewing.");
        }

        if (_cupState == "notdetected")
        {
            Console.WriteLine(" - Warning: There is no cup detected.");
        }

        if (_maintenanceState)
        {
            Console.WriteLine(" - Warning: The device is in maintenance mode.");
        }

        //Set state - brew for 30 seconds
        if (_cupState == "detected" && _brewingState == "notbrewing" && !_maintenanceState)
        {
            _brewingState = "brewing";
            _brewingTimer = 30;
        }

        //Send response
        byte[] responsePayload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject("Success"));
        return Task.FromResult(new MethodResponse(responsePayload, (int)StatusCode.Completed));
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Exception while handling \"StartBrewing\" command: {ex}");
        return Task.FromResult(new MethodResponse((int)StatusCode.BadRequest));
    }
}

Felhőtulajdonságok és nézetek

A felhőtulajdonságok és nézetek nem befolyásolják az eszközfejlesztő által az eszközmodell implementálásához írt kódot.