Definiowanie symulatora urządzenia do kawy

Ukończone

Deweloper urządzenia musi upewnić się, że urządzenie implementuje zachowania zdefiniowane w modelu urządzenia, aby usługa Azure IoT Central mogła monitorować urządzenie i zarządzać nim.

W przykładzie z włączonymi ekspresami do kawy IoT zasymulujesz urządzenie w celu zweryfikowania scenariusza przed połączeniem urządzenia fizycznego.

W tym miejscu zobaczysz, jak deweloper urządzenia używa modelu urządzenia zdefiniowanego w pliku CoffeeMaker.json, aby napisać aplikację w języku C#, która symuluje ekspres do kawy.

Konwencje technologii IoT Plug and Play

Urządzenia łączące się z usługą IoT Central powinny postępować zgodnie z zestawem konwencji IoT Plug and Play. Jedną z tych konwencji jest to, że urządzenie powinno wysyłać identyfikator modelu cyfrowej reprezentacji bliźniaczej (DTMI) podczas nawiązywania połączenia. Usługa DTMI umożliwia aplikacji usługi IoT Central przypisanie urządzenia do odpowiedniego szablonu urządzenia.

Zestawy SDK urządzeń Azure IoT obejmują obsługę konwencji IoT Plug and Play.

Przeglądanie kodu

Przykładowy kod dla repozytorium GitHub dokumentacji usługi Azure IoT Central zawiera przykładowy kod. Możesz otworzyć pliki CoffeeMaker.cs, Program.cs i Parameters.cs, aby wyświetlić cały kod.

W pliku Program.cs metoda wywołuje metodę Main SetupDeviceClientAsync do:

  • Aprowizuj urządzenie i wyślij dtmi:com:example:ConnectedCoffeeMaker;1 identyfikator modelu jako ładunek.
  • Utwórz wystąpienie klienta urządzenia w celu nawiązania połączenia z usługą IoT Central.
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;
}

Metoda główna tworzy następnie wystąpienie usługi CoffeeMaker i wywołuje PerformOperationsAsync metodę w celu obsługi interakcji z usługą IoT Central.

W pliku CoffeeMaker.csPerformOperationsAsync metoda:

  • Ustawia programy obsługi do odbierania SetMaintenanceMode wywołań zwrotnych poleceń i StartBrewing .
  • Ustawia procedurę obsługi do obsługi OptimalTemperature żądanych zmian właściwości w bliźniaczej reprezentacji urządzenia.
  • DeviceWarrantyExpired Aktualizacje zgłaszanej właściwości bliźniaczej reprezentacji urządzenia podczas początkowego uruchamiania.
  • Uruchamia pętlę w celu wysyłania danych telemetrycznych dotyczących temperatury i wilgotności, niezależnie od tego, czy jest ona obecnie zaparzana, czy filiżanka jest wykrywana co 1 sekundę.
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

Metoda SendTelemetryAsync wysyła dane telemetryczne w formacie, który określa model urządzenia.

//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);
}

Właściwości

Model określa nazwy właściwości i typy danych, aby synchronizować wartości właściwości między urządzeniem a usługą IoT Central za pośrednictwem bliźniaczej reprezentacji urządzenia. Metoda UpdateDeviceWarranty wysyła DeviceWarrantyExpired stan urządzenia, a metoda OptimalTemperatureUpdateCallbackAsync obsługuje OptimalTemperature zmiany pochodzące z usługi IoT Central.

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}.");
}

Polecenia

Model określa nazwy poleceń i parametry, których urządzenie powinno używać. Metody HandleMaintenanceModeCommand i HandleStartBrewingCommand obsługa poleceń wysyłanych z usługi IoT Central.

// 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));
    }
}

Właściwości i widoki w chmurze

Właściwości i widoki w chmurze nie mają wpływu na kod zapisywany przez dewelopera urządzenia w celu zaimplementowania modelu urządzenia.