Skip to content

Latest commit

 

History

History
389 lines (329 loc) · 23.5 KB

iot-central-connect-device-java.md

File metadata and controls

389 lines (329 loc) · 23.5 KB
authorms.authorms.servicems.topicms.date
dominicbetts
dobett
azure-iot
include
06/06/2023

Browse code

[!INCLUDE iot-authentication-device-connection-string]

Prerequisites

To complete the steps in this article, you need the following resources:

[!INCLUDE iot-central-prerequisites-basic]

Review the code

In the copy of the Microsoft Azure IoT SDK for Java you downloaded previously, open the azure-iot-sdk-java/iothub/device/iot-device-samples/pnp-device-sample/temperature-controller-device-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/device/TemperatureController.java file in a text editor.

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 command-line environment.

The main method:

  • Calls initializeAndProvisionDevice to set the dtmi:com:example:TemperatureController;2 model ID, use DPS to provision and register the device, create a DeviceClient instance, and connect to your IoT Central application. 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.
  • Creates command handlers for the getMaxMinReport and reboot commands.
  • Creates property update handlers for the writable targetTemperature properties.
  • Sends initial values for the properties in the Device Information interface and the Device Memory and Serial Number properties.
  • Starts a thread to send temperature telemetry from the two thermostats and update the maxTempSinceLastReboot property every five seconds.
publicstaticvoidmain(String[] args) throwsException { // ...switch (deviceSecurityType.toLowerCase()) { case"dps": { if (validateArgsForDpsFlow()) { initializeAndProvisionDevice(); break; } thrownewIllegalArgumentException("Required environment variables are not set for DPS flow, please recheck your environment."); } case"connectionstring": { // ... } default: { // ... } } deviceClient.subscribeToMethods(newMethodCallback(), null); deviceClient.subscribeToDesiredPropertiesAsync( { (twin, context) -> TwinCollectiondesiredProperties = twin.getDesiredProperties(); for (StringdesiredPropertyKey : desiredProperties.keySet()) { TargetTemperatureUpdateCallback.onPropertyChanged(newProperty(desiredPropertyKey, desiredProperties.get(desiredPropertyKey)), null); } }, null, (exception, context) -> { if (exception == null) { log.info("Successfully subscribed to desired properties. Getting initial state"); deviceClient.getTwinAsync( (twin, getTwinException, getTwinContext) -> { log.info("Initial twin state received"); log.info(twin.toString()); }, null); } else { log.info("Failed to subscribe to desired properties. Error code {}", exception.getStatusCode()); System.exit(-1); } }, null); updateDeviceInformation(); sendDeviceMemory(); sendDeviceSerialNumber(); finalAtomicBooleantemperatureReset = newAtomicBoolean(true); maxTemperature.put(THERMOSTAT_1, 0.0d); maxTemperature.put(THERMOSTAT_2, 0.0d); newThread(newRunnable() { @SneakyThrows({InterruptedException.class, IOException.class}) @Overridepublicvoidrun() { while (true) { if (temperatureReset.get()) { // Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component.temperature.put(THERMOSTAT_1, BigDecimal.valueOf(random.nextDouble() * 40 + 5).setScale(1, RoundingMode.HALF_UP).doubleValue()); temperature.put(THERMOSTAT_2, BigDecimal.valueOf(random.nextDouble() * 40 + 5).setScale(1, RoundingMode.HALF_UP).doubleValue()); } sendTemperatureReading(THERMOSTAT_1); sendTemperatureReading(THERMOSTAT_2); temperatureReset.set(temperature.get(THERMOSTAT_1) == 0 && temperature.get(THERMOSTAT_2) == 0); Thread.sleep(5 * 1000); } } }).start(); }

The initializeAndProvisionDevice method shows how the device uses DPS to register and connect to IoT Central. The payload includes the model ID that IoT Central uses to assign a device to a device template:

privatestaticvoidinitializeAndProvisionDevice() throwsException { SecurityProviderSymmetricKeysecurityClientSymmetricKey = newSecurityProviderSymmetricKey(deviceSymmetricKey.getBytes(), registrationId); ProvisioningDeviceClientprovisioningDeviceClient; ProvisioningStatusprovisioningStatus = newProvisioningStatus(); provisioningDeviceClient = ProvisioningDeviceClient.create(globalEndpoint, scopeId, provisioningProtocol, securityClientSymmetricKey); AdditionalDataadditionalData = newAdditionalData(); additionalData.setProvisioningPayload(com.microsoft.azure.sdk.iot.provisioning.device.plugandplay.PnpHelper.createDpsPayload(MODEL_ID)); ProvisioningDeviceClientRegistrationResultregistrationResult = provisioningDeviceClient.registerDeviceSync(additionalData); ClientOptionsoptions = ClientOptions.builder().modelId(MODEL_ID).build(); if (registrationResult.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED) { System.out.println("IotHUb Uri : " + registrationResult.getIothubUri()); System.out.println("Device ID : " + registrationResult.getDeviceId()); StringiotHubUri = registrationResult.getIothubUri(); StringdeviceId = registrationResult.getDeviceId(); log.debug("Opening the device client."); deviceClient = newDeviceClient(iotHubUri, deviceId, securityClientSymmetricKey, IotHubClientProtocol.MQTT, options); deviceClient.open(true); } }

The sendTemperatureTelemetry method shows how the device sends the temperature telemetry from a component to IoT Central. This method uses the PnpConvention class to create the message:

privatestaticvoidsendTemperatureTelemetry(StringcomponentName) { StringtelemetryName = "temperature"; doublecurrentTemperature = temperature.get(componentName); Messagemessage = PnpConvention.createIotHubMessageUtf8(telemetryName, currentTemperature, componentName); deviceClient.sendEventAsync(message, newMessageIotHubEventCallback(), message); // Add the current temperature entry to the list of temperature readings.Map<Date, Double> currentReadings; if (temperatureReadings.containsKey(componentName)) { currentReadings = temperatureReadings.get(componentName); } else { currentReadings = newHashMap<>(); } currentReadings.put(newDate(), currentTemperature); temperatureReadings.put(componentName, currentReadings); }

The updateMaxTemperatureSinceLastReboot method sends a maxTempSinceLastReboot property update from a component to IoT Central. This method uses the PnpConvention class to create the patch:

privatestaticvoidupdateMaxTemperatureSinceLastReboot(StringcomponentName) throwsIOException { StringpropertyName = "maxTempSinceLastReboot"; doublemaxTemp = maxTemperature.get(componentName); TwinCollectionreportedProperty = PnpConvention.createComponentPropertyPatch(propertyName, maxTemp, componentName); deviceClient.updateReportedPropertiesAsync(reportedProperty, sendReportedPropertiesResponseCallback, null); log.debug("Property: Update - {\"{}\": {}°C} is {}.", propertyName, maxTemp, StatusCode.COMPLETED); }

The TargetTemperatureUpdateCallback class contains the onPropertyChanged method to handle writable property updates to a component from IoT Central. This method uses the PnpConvention class to create the response:

privatestaticclassTargetTemperatureUpdateCallback { finalstaticStringpropertyName = "targetTemperature"; @SneakyThrows(InterruptedException.class) publicstaticvoidonPropertyChanged(Propertyproperty, Objectcontext) { StringcomponentName = (String) context; if (property.getKey().equalsIgnoreCase(componentName)) { doubletargetTemperature = (double) ((TwinCollection) property.getValue()).get(propertyName); log.debug("Property: Received - component=\"{}\", {\"{}\": {}°C}.", componentName, propertyName, targetTemperature); TwinCollectionpendingPropertyPatch = PnpConvention.createComponentWritablePropertyResponse( propertyName, targetTemperature, componentName, StatusCode.IN_PROGRESS.value, property.getVersion().longValue(), null); deviceClient.updateReportedPropertiesAsync(pendingPropertyPatch, sendReportedPropertiesResponseCallback, null); log.debug("Property: Update - component=\"{}\", {\"{}\": {}°C} is {}", componentName, propertyName, targetTemperature, StatusCode.IN_PROGRESS); // Update temperature in 2 stepsdoublestep = (targetTemperature - temperature.get(componentName)) / 2; for (inti = 1; i <=2; i++) { temperature.put(componentName, BigDecimal.valueOf(temperature.get(componentName) + step).setScale(1, RoundingMode.HALF_UP).doubleValue()); Thread.sleep(5 * 1000); } TwinCollectioncompletedPropertyPatch = PnpConvention.createComponentWritablePropertyResponse( propertyName, temperature.get(componentName), componentName, StatusCode.COMPLETED.value, property.getVersion().longValue(), "Successfully updated target temperature."); deviceClient.updateReportedPropertiesAsync(completedPropertyPatch, sendReportedPropertiesResponseCallback, null); log.debug("Property: Update - {\"{}\": {}°C} is {}", propertyName, temperature.get(componentName), StatusCode.COMPLETED); } else { log.debug("Property: Received an unrecognized property update from service."); } } }

The MethodCallback class contains the onMethodInvoked method to handle component commands called from IoT Central:

privatestaticclassMethodCallbackimplementscom.microsoft.azure.sdk.iot.device.twin.MethodCallback { finalStringreboot = "reboot"; finalStringgetMaxMinReport1 = "thermostat1*getMaxMinReport"; finalStringgetMaxMinReport2 = "thermostat2*getMaxMinReport"; @SneakyThrows(InterruptedException.class) @OverridepublicDirectMethodResponseonMethodInvoked(StringmethodName, DirectMethodPayloadmethodData, Objectcontext) { StringjsonRequest = methodData.getPayload(String.class); switch (methodName) { casereboot: intdelay = getCommandRequestValue(jsonRequest, Integer.class); log.debug("Command: Received - Rebooting thermostat (resetting temperature reading to 0°C after {} seconds).", delay); Thread.sleep(delay * 1000L); temperature.put(THERMOSTAT_1, 0.0d); temperature.put(THERMOSTAT_2, 0.0d); maxTemperature.put(THERMOSTAT_1, 0.0d); maxTemperature.put(THERMOSTAT_2, 0.0d); temperatureReadings.clear(); returnnewDirectMethodResponse(StatusCode.COMPLETED.value, null); casegetMaxMinReport1: casegetMaxMinReport2: String[] words = methodName.split("\\*"); StringcomponentName = words[0]; if (temperatureReadings.containsKey(componentName)) { Datesince = getCommandRequestValue(jsonRequest, Date.class); log.debug("Command: Received - component=\"{}\", generating min, max, avg temperature report since {}", componentName, since); Map<Date, Double> allReadings = temperatureReadings.get(componentName); Map<Date, Double> filteredReadings = allReadings.entrySet().stream() .filter(map -> map.getKey().after(since)) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); if (!filteredReadings.isEmpty()) { SimpleDateFormatsdf = newSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); doublemaxTemp = Collections.max(filteredReadings.values()); doubleminTemp = Collections.min(filteredReadings.values()); doubleavgTemp = filteredReadings.values().stream().mapToDouble(Double::doubleValue).average().orElse(Double.NaN); StringstartTime = sdf.format(Collections.min(filteredReadings.keySet())); StringendTime = sdf.format(Collections.max(filteredReadings.keySet())); StringresponsePayload = String.format( "{\"maxTemp\": %.1f, \"minTemp\": %.1f, \"avgTemp\": %.1f, \"startTime\": \"%s\", \"endTime\": \"%s\"}", maxTemp, minTemp, avgTemp, startTime, endTime); log.debug("Command: MaxMinReport since {}: \"maxTemp\": {}°C, \"minTemp\": {}°C, \"avgTemp\": {}°C, \"startTime\": {}, \"endTime\": {}", since, maxTemp, minTemp, avgTemp, startTime, endTime); returnnewDirectMethodResponse(StatusCode.COMPLETED.value, responsePayload); } log.debug("Command: component=\"{}\", no relevant readings found since {}, cannot generate any report.", componentName, since); returnnewDirectMethodResponse(StatusCode.NOT_FOUND.value, null); } log.debug("Command: component=\"{}\", no temperature readings sent yet, cannot generate any report.", componentName); returnnewDirectMethodResponse(StatusCode.NOT_FOUND.value, null); default: log.debug("Command: command=\"{}\" is not implemented, no action taken.", methodName); returnnewDirectMethodResponse(StatusCode.NOT_FOUND.value, null); } } }

Get connection information

[!INCLUDE iot-central-connection-configuration]

On Windows, navigate to the root folder of the Azure IoT SDK for Java repository you downloaded.

Run the following command to build the sample application:

mvn install -T 2C -DskipTests 

Run the code

To run the sample application, open a command-line environment and navigate to the folder azure-iot-sdk-java/iothub/device/iot-device-samples/pnp-device-sample/temperature-controller-device-sample folder that contains the src folder with the TemperatureController.java sample file.

[!INCLUDE iot-central-connection-environment]

Run the sample:

mvn exec:java -Dexec.mainClass="samples.com.microsoft.azure.sdk.iot.device.TemperatureController" 

The following output shows the device registering and connecting to IoT Central. The sample starts sending telemetry:

2021-03-30 15:33:25.138 DEBUG TemperatureController:123 - Initialize the device client. Waiting for Provisioning Service to register Waiting for Provisioning Service to register IotHUb Uri : iotc-60a.....azure-devices.net Device ID : sample-device-01 2021-03-30 15:33:38.294 DEBUG TemperatureController:247 - Opening the device client. 2021-03-30 15:33:38.307 INFO ExponentialBackoffWithJitter:98 - NOTE: A new instance of ExponentialBackoffWithJitter has been created with the following properties. Retry Count: 2147483647, Min Backoff Interval: 100, Max Backoff Interval: 10000, Max Time Between Retries: 100, Fast Retry Enabled: true 2021-03-30 15:33:38.321 INFO ExponentialBackoffWithJitter:98 - NOTE: A new instance of ExponentialBackoffWithJitter has been created with the following properties. Retry Count: 2147483647, Min Backoff Interval: 100, Max Backoff Interval: 10000, Max Time Between Retries: 100, Fast Retry Enabled: true 2021-03-30 15:33:38.427 DEBUG MqttIotHubConnection:274 - Opening MQTT connection... 2021-03-30 15:33:38.427 DEBUG Mqtt:123 - Sending MQTT CONNECT packet... 2021-03-30 15:33:44.628 DEBUG Mqtt:126 - Sent MQTT CONNECT packet was acknowledged 2021-03-30 15:33:44.630 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic devices/sample-device-01/messages/devicebound/# 2021-03-30 15:33:44.731 DEBUG Mqtt:261 - Sent MQTT SUBSCRIBE packet for topic devices/sample-device-01/messages/devicebound/# was acknowledged 2021-03-30 15:33:44.733 DEBUG MqttIotHubConnection:279 - MQTT connection opened successfully 2021-03-30 15:33:44.733 DEBUG IotHubTransport:302 - The connection to the IoT Hub has been established 2021-03-30 15:33:44.734 INFO IotHubTransport:1429 - Updating transport status to new status CONNECTED with reason CONNECTION_OK 2021-03-30 15:33:44.735 DEBUG IotHubTransport:1439 - Invoking connection status callbacks with new status details 2021-03-30 15:33:44.739 DEBUG IotHubTransport:394 - Client connection opened successfully 2021-03-30 15:33:44.740 INFO DeviceClient:438 - Device client opened successfully 2021-03-30 15:33:44.740 DEBUG TemperatureController:152 - Set handler for "reboot" command. 2021-03-30 15:33:44.742 DEBUG TemperatureController:153 - Set handler for "getMaxMinReport" command. 2021-03-30 15:33:44.774 INFO IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] Device Operation Type [DEVICE_OPERATION_METHOD_SUBSCRIBE_REQUEST] ) 2021-03-30 15:33:44.774 DEBUG TemperatureController:156 - Set handler to receive "targetTemperature" updates. 2021-03-30 15:33:44.775 INFO IotHubTransport:1344 - Sending message ( Message details: Correlation Id [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] Device Operation Type [DEVICE_OPERATION_METHOD_SUBSCRIBE_REQUEST] ) 2021-03-30 15:33:44.779 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic $iothub/methods/POST/# 2021-03-30 15:33:44.793 INFO IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [cccc2222-dd33-4444-55ee-666666ffffff] Message Id [dddd3333-ee44-5555-66ff-777777aaaaaa] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] ) 2021-03-30 15:33:44.794 INFO IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [eeee4444-ff55-6666-77aa-888888bbbbbb] Message Id [ffff5555-aa66-7777-88bb-999999cccccc] Request Id [0] Device Operation Type [DEVICE_OPERATION_TWIN_GET_REQUEST] ) 2021-03-30 15:33:44.819 INFO IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [aaaa6666-bb77-8888-99cc-000000dddddd] Message Id [aaaa0000-bb11-2222-33cc-444444dddddd] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] ) 2021-03-30 15:33:44.881 DEBUG Mqtt:261 - Sent MQTT SUBSCRIBE packet for topic $iothub/methods/POST/# was acknowledged 2021-03-30 15:33:44.882 INFO IotHubTransport:1344 - Sending message ( Message details: Correlation Id [cccc2222-dd33-4444-55ee-666666ffffff] Message Id [dddd3333-ee44-5555-66ff-777777aaaaaa] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] ) 2021-03-30 15:33:44.882 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic $iothub/twin/res/# 2021-03-30 15:33:44.893 INFO IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [bbbb1111-cc22-3333-44dd-555555eeeeee] Message Id [cccc2222-dd33-4444-55ee-666666ffffff] Request Id [1] Device Operation Type [DEVICE_OPERATION_TWIN_UPDATE_REPORTED_PROPERTIES_REQUEST] ) 2021-03-30 15:33:44.904 DEBUG TemperatureController:423 - Property: Update - component = "deviceInformation" is COMPLETED. 2021-03-30 15:33:44.915 INFO IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [dddd3333-ee44-5555-66ff-777777aaaaaa] Message Id [eeee4444-ff55-6666-77aa-888888bbbbbb] ) 2021-03-30 15:33:44.915 DEBUG TemperatureController:434 - Telemetry: Sent - {"workingSet": 1024.0KiB } 2021-03-30 15:33:44.915 INFO IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [ffff5555-aa66-7777-88bb-999999cccccc] Message Id [aaaa6666-bb77-8888-99cc-000000dddddd] Request Id [2] Device Operation Type [DEVICE_OPERATION_TWIN_UPDATE_REPORTED_PROPERTIES_REQUEST] ) 2021-03-30 15:33:44.916 DEBUG TemperatureController:442 - Property: Update - {"serialNumber": SR-123456} is COMPLETED 2021-03-30 15:33:44.927 INFO IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] ) 2021-03-30 15:33:44.927 DEBUG TemperatureController:461 - Telemetry: Sent - {"temperature": 5.8°C} with message Id bbbb1111-cc22-3333-44dd-555555eeeeee. 

[!INCLUDE iot-central-monitor-thermostat]

You can see how the device responds to commands and property updates:

2021-03-30 15:43:57.133 DEBUG TemperatureController:309 - Command: Received - component="thermostat1", generating min, max, avg temperature report since Tue Mar 30 06:00:00 BST 2021 2021-03-30 15:43:57.153 DEBUG TemperatureController:332 - Command: MaxMinReport since Tue Mar 30 06:00:00 BST 2021: "maxTemp": 35.6°C, "minTemp": 35.6°C, "avgTemp": 35.6°C, "startTime": 2021-03-30T15:43:41Z, "endTime": 2021-03-30T15:43:56Z 2021-03-30 15:43:57.394 DEBUG TemperatureController:502 - Command - Response from IoT Hub: command name=null, status=OK_EMPTY ... 2021-03-30 15:48:47.808 DEBUG TemperatureController:372 - Property: Received - component="thermostat2", {"targetTemperature": 67.0°C}. 2021-03-30 15:48:47.837 DEBUG TemperatureController:382 - Property: Update - component="thermostat2", {"targetTemperature": 67.0°C} is IN_PROGRESS 
close