author | ms.author | ms.service | ms.topic | ms.date |
---|---|---|---|---|
dominicbetts | dobett | azure-iot | include | 06/06/2023 |
[!INCLUDE iot-authentication-device-connection-string]
To complete the steps in this article, you need the following resources:
[!INCLUDE iot-central-prerequisites-basic]
A development machine with Visual Studio (Community, Professional, or Enterprise).
A local copy of the Microsoft Azure IoT SDK for C# (.NET) GitHub repository that contains the sample code. Use this link to download a copy of the repository: Download ZIP. Then unzip the file to a suitable location on your local machine.
In the copy of the Microsoft Azure IoT SDK for C# repository you downloaded previously, open the azure-iot-sdk-csharp-main\azureiot.sln solution file in Visual Studio. In Solution Explorer, expand the PnpDeviceSamples > TemperatureController folder and open the Program.cs and TemperatureControllerSample.cs files to view the code for this sample.
The sample implements the multiple-component Temperature Controller Digital Twin Definition Language model.
When you run the sample to connect to IoT Central, it uses the Device Provisioning Service (DPS) to register the device and generate a connection string. The sample retrieves the DPS connection information it needs from the environment.
In Program.cs, the Main
method calls SetupDeviceClientAsync
to:
- Use the model ID
dtmi:com:example:TemperatureController;2
when it provisions the device with DPS. IoT Central uses the model ID to identify or generate the device template for this device. To learn more, see Assign a device to a device template. - Create a DeviceClient instance to connect to IoT Central.
privatestaticasyncTask<DeviceClient>SetupDeviceClientAsync(Parametersparameters,CancellationTokencancellationToken){DeviceClientdeviceClient;switch(parameters.DeviceSecurityType.ToLowerInvariant()){case"dps":DeviceRegistrationResultdpsRegistrationResult=awaitProvisionDeviceAsync(parameters,cancellationToken);varauthMethod=newDeviceAuthenticationWithRegistrySymmetricKey(dpsRegistrationResult.DeviceId,parameters.DeviceSymmetricKey);deviceClient=InitializeDeviceClient(dpsRegistrationResult.AssignedHub,authMethod);break;case"connectionstring":// ...default:// ...}returndeviceClient;}
The main method then creates a TemperatureControllerSample instance and calls the PerformOperationsAsync
method to handle the interactions with IoT Central.
In TemperatureControllerSample.cs, the PerformOperationsAsync
method:
- Sets a handler for the reboot command on the default component.
- Sets handlers for the getMaxMinReport commands on the two thermostat components.
- Sets handlers to receive target temperature property updates on the two thermostat components.
- Sends initial device information property updates.
- Periodically sends temperature telemetry from the two thermostat components.
- Periodically sends working set telemetry from the default component.
- Sends the maximum temperature since the last reboot whenever a new maximum temperature is reached in the two thermostat components.
publicasyncTaskPerformOperationsAsync(CancellationTokencancellationToken){await_deviceClient.SetMethodHandlerAsync("reboot",HandleRebootCommandAsync,_deviceClient,cancellationToken);// For a component-level command, the command name is in the format "<component-name>*<command-name>".await_deviceClient.SetMethodHandlerAsync("thermostat1*getMaxMinReport",HandleMaxMinReportCommand,Thermostat1,cancellationToken);await_deviceClient.SetMethodHandlerAsync("thermostat2*getMaxMinReport",HandleMaxMinReportCommand,Thermostat2,cancellationToken);await_deviceClient.SetDesiredPropertyUpdateCallbackAsync(SetDesiredPropertyUpdateCallback,null,cancellationToken);_desiredPropertyUpdateCallbacks.Add(Thermostat1,TargetTemperatureUpdateCallbackAsync);_desiredPropertyUpdateCallbacks.Add(Thermostat2,TargetTemperatureUpdateCallbackAsync);awaitUpdateDeviceInformationAsync(cancellationToken);awaitSendDeviceSerialNumberAsync(cancellationToken);booltemperatureReset=true;_maxTemp[Thermostat1]=0d;_maxTemp[Thermostat2]=0d;while(!cancellationToken.IsCancellationRequested){if(temperatureReset){// Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component._temperature[Thermostat1]=Math.Round(s_random.NextDouble()*40.0+5.0,1);_temperature[Thermostat2]=Math.Round(s_random.NextDouble()*40.0+5.0,1);}awaitSendTemperatureAsync(Thermostat1,cancellationToken);awaitSendTemperatureAsync(Thermostat2,cancellationToken);awaitSendDeviceMemoryAsync(cancellationToken);temperatureReset=_temperature[Thermostat1]==0&&_temperature[Thermostat2]==0;awaitTask.Delay(5*1000);}}
The SendTemperatureAsync
method shows how the device sends the temperature telemetry from a component to IoT Central. The SendTemperatureTelemetryAsync
method uses the PnpConvention
class to build the message:
privateasyncTaskSendTemperatureAsync(stringcomponentName,CancellationTokencancellationToken){awaitSendTemperatureTelemetryAsync(componentName,cancellationToken);doublemaxTemp=_temperatureReadingsDateTimeOffset[componentName].Values.Max<double>();if(maxTemp>_maxTemp[componentName]){_maxTemp[componentName]=maxTemp;awaitUpdateMaxTemperatureSinceLastRebootAsync(componentName,cancellationToken);}}privateasyncTaskSendTemperatureTelemetryAsync(stringcomponentName,CancellationTokencancellationToken){conststringtelemetryName="temperature";doublecurrentTemperature=_temperature[componentName];usingMessagemsg=PnpConvention.CreateMessage(telemetryName,currentTemperature,componentName);await_deviceClient.SendEventAsync(msg,cancellationToken);if(_temperatureReadingsDateTimeOffset.ContainsKey(componentName)){_temperatureReadingsDateTimeOffset[componentName].TryAdd(DateTimeOffset.UtcNow,currentTemperature);}else{_temperatureReadingsDateTimeOffset.TryAdd(componentName,newDictionary<DateTimeOffset,double>{{DateTimeOffset.UtcNow,currentTemperature},});}}
The UpdateMaxTemperatureSinceLastRebootAsync
method sends a maxTempSinceLastReboot
property update to IoT Central. This method uses the PnpConvention
class to create the patch:
privateasyncTaskUpdateMaxTemperatureSinceLastRebootAsync(stringcomponentName,CancellationTokencancellationToken){conststringpropertyName="maxTempSinceLastReboot";doublemaxTemp=_maxTemp[componentName];TwinCollectionreportedProperties=PnpConvention.CreateComponentPropertyPatch(componentName,propertyName,maxTemp);await_deviceClient.UpdateReportedPropertiesAsync(reportedProperties,cancellationToken);}
The TargetTemperatureUpdateCallbackAsync
method handles the writable target temperature property update from IoT Central. This method uses the PnpConvention
class to read the property update message and construct the response:
privateasyncTaskTargetTemperatureUpdateCallbackAsync(TwinCollectiondesiredProperties,objectuserContext){conststringpropertyName="targetTemperature";stringcomponentName=(string)userContext;booltargetTempUpdateReceived=PnpConvention.TryGetPropertyFromTwin(desiredProperties,propertyName,outdoubletargetTemperature,componentName);if(!targetTempUpdateReceived){return;}TwinCollectionpendingReportedProperty=PnpConvention.CreateComponentWritablePropertyResponse(componentName,propertyName,targetTemperature,(int)StatusCode.InProgress,desiredProperties.Version);await_deviceClient.UpdateReportedPropertiesAsync(pendingReportedProperty);// Update Temperature in 2 stepsdoublestep=(targetTemperature-_temperature[componentName])/2d;for(inti=1;i<=2;i++){_temperature[componentName]=Math.Round(_temperature[componentName]+step,1);awaitTask.Delay(6*1000);}TwinCollectioncompletedReportedProperty=PnpConvention.CreateComponentWritablePropertyResponse(componentName,propertyName,_temperature[componentName],(int)StatusCode.Completed,desiredProperties.Version,"Successfully updated target temperature");await_deviceClient.UpdateReportedPropertiesAsync(completedReportedProperty);}
The HandleMaxMinReportCommand
method handles the commands for the components called from IoT Central:
privateTask<MethodResponse>HandleMaxMinReportCommand(MethodRequestrequest,objectuserContext){try{stringcomponentName=(string)userContext;DateTimesinceInUtc=JsonConvert.DeserializeObject<DateTime>(request.DataAsJson);varsinceInDateTimeOffset=newDateTimeOffset(sinceInUtc);if(_temperatureReadingsDateTimeOffset.ContainsKey(componentName)){Dictionary<DateTimeOffset,double>allReadings=_temperatureReadingsDateTimeOffset[componentName];Dictionary<DateTimeOffset,double>filteredReadings=allReadings.Where(i =>i.Key>sinceInDateTimeOffset).ToDictionary(i =>i.Key, i =>i.Value);if(filteredReadings!=null&&filteredReadings.Any()){varreport=new{maxTemp=filteredReadings.Values.Max<double>(),minTemp=filteredReadings.Values.Min<double>(),avgTemp=filteredReadings.Values.Average(),startTime=filteredReadings.Keys.Min(),endTime=filteredReadings.Keys.Max(),};byte[]responsePayload=Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(report));returnTask.FromResult(newMethodResponse(responsePayload,(int)StatusCode.Completed));}returnTask.FromResult(newMethodResponse((int)StatusCode.NotFound));}returnTask.FromResult(newMethodResponse((int)StatusCode.NotFound));}catch(JsonReaderExceptionex){// ...}}
[!INCLUDE iot-central-connection-configuration]
Note
Set up TemperatureController as startup project before you run the code.
To run the sample application in Visual Studio:
In Solution Explorer, select the PnpDeviceSamples > TemperatureController project file.
Navigate to Project > TemperatureController Properties > Debug. Then add the following environment variables to the project:
Name Value IOTHUB_DEVICE_SECURITY_TYPE DPS IOTHUB_DEVICE_DPS_ENDPOINT global.azure-devices-provisioning.net IOTHUB_DEVICE_DPS_ID_SCOPE The ID scope value you made a note of previously. IOTHUB_DEVICE_DPS_DEVICE_ID sample-device-01 IOTHUB_DEVICE_DPS_DEVICE_KEY The generated device key value you made a note of previously.
You can now run and debug the sample in Visual Studio.
The following output shows the device registering and connecting to IoT Central. The sample starts sending telemetry:
[03/31/2021 14:43:17]info: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Press Control+C to quit the sample. [03/31/2021 14:43:17]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Set up the device client. [03/31/2021 14:43:18]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Initializing via DPS [03/31/2021 14:43:27]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Set handler for 'reboot' command. [03/31/2021 14:43:27]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Connection status change registered - status=Connected, reason=Connection_Ok. [03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Set handler for "getMaxMinReport" command. [03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Set handler to receive 'targetTemperature' updates. [03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Property: Update - component = 'deviceInformation', properties update is complete. [03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Property: Update - { "serialNumber": "SR-123456" } is complete. [03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Telemetry: Sent - component="thermostat1", { "temperature": 34.2 } in °C. [03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Property: Update - component="thermostat1", { "maxTempSinceLastReboot": 34.2 } in °C is complete. [03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Telemetry: Sent - component="thermostat2", { "temperature": 25.1 } in °C. [03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Property: Update - component="thermostat2", { "maxTempSinceLastReboot": 25.1 } in °C is complete. [03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Telemetry: Sent - {"workingSet":31412} in KB.
[!INCLUDE iot-central-monitor-thermostat]
You can see how the device responds to commands and property updates:
[03/31/2021 14:47:00]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Command: Received - component="thermostat2", generating max, min and avg temperature report since 31/03/2021 06:00:00. [03/31/2021 14:47:00]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Command: component="thermostat2", MaxMinReport since 31/03/2021 06:00:00: maxTemp=36.4, minTemp=36.4, avgTemp=36.4, startTime=31/03/2021 14:46:33, endTime=31/03/2021 14:46:55 ... [03/31/2021 14:46:36]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Property: Received - component="thermostat1", { "targetTemperature": 67°C }. [03/31/2021 14:46:36]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Property: Update - component="thermostat1", {"targetTemperature": 67 } in °C is InProgress. [03/31/2021 14:46:49]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Property: Update - component="thermostat1", {"targetTemperature": 67 } in °C is Completed [03/31/2021 14:46:49]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0] Telemetry: Sent - component="thermostat1", { "temperature": 67 } in °C.