Definiowanie symulatora urządzenia do kawy
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ń iStartBrewing
. - 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.