Skip to content

Latest commit

 

History

History
253 lines (195 loc) · 9.84 KB

iot-central-connect-device-python.md

File metadata and controls

253 lines (195 loc) · 9.84 KB
author ms.author ms.service ms.topic ms.date
dominicbetts
dobett
iot-pnp
include
11/24/2020

Prerequisites

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

  • An Azure IoT Central application created using the Custom application template. For more information, see the create an application quickstart. The application must have been created on or after 14 July 2020.
  • A development machine with Python version 3.7 or later installed. You can run python --version at the command line to check your version. Python is available for a wide variety of operating systems. The instructions in this tutorial assume you're running the python command at the Windows command prompt.
  • A local copy of the Microsoft Azure IoT SDK for Python 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.

Review the code

In the copy of the Microsoft Azure IoT SDK for Python you downloaded previously, open the azure-iot-sdk-python/azure-iot-device/samples/pnp/simple_thermostat.py file in a text editor.

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 function:

  • Uses DPS to provision the device. The provisioning information includes the model ID. IoT Central uses the model ID to identify or generate the device template for this device. To learn more, see Associate a device with a device template.
  • Creates a Device_client object and sets the dtmi:com:example:Thermostat;1 model ID before it opens the connection.
  • Sends the maxTempSinceLastReboot property to IoT Central.
  • Creates a listener for the getMaxMinReport command.
  • Creates property listener, to listen for writable property updates.
  • Starts a loop to send temperature telemetry every 10 seconds.
async def main():
    switch = os.getenv("IOTHUB_DEVICE_SECURITY_TYPE")
    if switch == "DPS":
        provisioning_host = (
            os.getenv("IOTHUB_DEVICE_DPS_ENDPOINT")
            if os.getenv("IOTHUB_DEVICE_DPS_ENDPOINT")
            else "global.azure-devices-provisioning.net"
        )
        id_scope = os.getenv("IOTHUB_DEVICE_DPS_ID_SCOPE")
        registration_id = os.getenv("IOTHUB_DEVICE_DPS_DEVICE_ID")
        symmetric_key = os.getenv("IOTHUB_DEVICE_DPS_DEVICE_KEY")

        registration_result = await provision_device(
            provisioning_host, id_scope, registration_id, symmetric_key, model_id
        )

        if registration_result.status == "assigned":

            device_client = IoTHubDeviceClient.create_from_symmetric_key(
                symmetric_key=symmetric_key,
                hostname=registration_result.registration_state.assigned_hub,
                device_id=registration_result.registration_state.device_id,
                product_info=model_id,
            )
        else:
            raise RuntimeError(
                "Could not provision device. Aborting Plug and Play device connection."
            )

    elif switch == "connectionString":

        # ...

    # Connect the client.
    await device_client.connect()

    max_temp = 10.96  # Initial Max Temp otherwise will not pass certification
    await device_client.patch_twin_reported_properties({"maxTempSinceLastReboot": max_temp})

    listeners = asyncio.gather(
        execute_command_listener(
            device_client,
            method_name="getMaxMinReport",
            user_command_handler=max_min_handler,
            create_user_response_handler=create_max_min_report_response,
        ),
        execute_property_listener(device_client),
    )

    async def send_telemetry():
        global max_temp
        global min_temp
        current_avg_idx = 0

        while True:
            current_temp = random.randrange(10, 50)
            if not max_temp:
                max_temp = current_temp
            elif current_temp > max_temp:
                max_temp = current_temp

            if not min_temp:
                min_temp = current_temp
            elif current_temp < min_temp:
                min_temp = current_temp

            avg_temp_list[current_avg_idx] = current_temp
            current_avg_idx = (current_avg_idx + 1) % moving_window_size

            temperature_msg1 = {"temperature": current_temp}
            await send_telemetry_from_thermostat(device_client, temperature_msg1)
            await asyncio.sleep(8)

    send_telemetry_task = asyncio.create_task(send_telemetry())

    # ...

The provision_device function uses DPS to provision the device and register it with IoT Central. The function includes the device model ID, which IoT Central uses to associate a device with a device template, in the provisioning payload:

async def provision_device(provisioning_host, id_scope, registration_id, symmetric_key, model_id):
    provisioning_device_client = ProvisioningDeviceClient.create_from_symmetric_key(
        provisioning_host=provisioning_host,
        registration_id=registration_id,
        id_scope=id_scope,
        symmetric_key=symmetric_key,
    )
    provisioning_device_client.provisioning_payload = {"modelId": model_id}
    return await provisioning_device_client.register()

The execute_command_listener function handles command requests, runs the max_min_handler function when the device receives the getMaxMinReport command, and runs the create_max_min_report_response function to generate the response:

async def execute_command_listener(
    device_client, method_name, user_command_handler, create_user_response_handler
):
    while True:
        if method_name:
            command_name = method_name
        else:
            command_name = None

        command_request = await device_client.receive_method_request(command_name)
        print("Command request received with payload")
        print(command_request.payload)

        values = {}
        if not command_request.payload:
            print("Payload was empty.")
        else:
            values = command_request.payload

        await user_command_handler(values)

        response_status = 200
        response_payload = create_user_response_handler(values)

        command_response = MethodResponse.create_from_method_request(
            command_request, response_status, response_payload
        )

        try:
            await device_client.send_method_response(command_response)
        except Exception:
            print("responding to the {command} command failed".format(command=method_name))

The async def execute_property_listener handles writable property updates such as targetTemperature and generates the JSON response:

async def execute_property_listener(device_client):
    ignore_keys = ["__t", "$version"]
    while True:
        patch = await device_client.receive_twin_desired_properties_patch()  # blocking call

        print("the data in the desired properties patch was: {}".format(patch))

        version = patch["$version"]
        prop_dict = {}

        for prop_name, prop_value in patch.items():
            if prop_name in ignore_keys:
                continue
            else:
                prop_dict[prop_name] = {
                    "ac": 200,
                    "ad": "Successfully executed patch",
                    "av": version,
                    "value": prop_value,
                }

        await device_client.patch_twin_reported_properties(prop_dict)

The send_telemetry_from_thermostat function sends the telemetry messages to IoT Central:

async def send_telemetry_from_thermostat(device_client, telemetry_msg):
    msg = Message(json.dumps(telemetry_msg))
    msg.content_encoding = "utf-8"
    msg.content_type = "application/json"
    print("Sent message")
    await device_client.send_message(msg)

Get connection information

[!INCLUDE iot-central-connection-configuration]

Run the code

To run the sample application, open a command-line environment and navigate to the folder azure-iot-sdk-python/azure-iot-device/samples/pnp folder that contains the simple_thermostat.py sample file.

[!INCLUDE iot-central-connection-environment]

Install the required packages:

pip install azure-iot-device

Run the sample:

python simple_thermostat.py

The following output shows the device registering and connecting to IoT Central. The sample sends the maxTempSinceLastReboot property before it starts sending telemetry:

Device was assigned
iotc-.......azure-devices.net
sample-device-01
Listening for command requests and property updates
Press Q to quit
Sending telemetry for temperature
Sent message
Sent message
Sent message

[!INCLUDE iot-central-monitor-thermostat]

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

Sent message
the data in the desired properties patch was: {'targetTemperature': {'value': 86.3}, '$version': 2}
Sent message

...

Sent message
Command request received with payload
2020-10-14T08:00:00.000Z
Will return the max, min and average temperature from the specified time 2020-10-14T08:00:00.000Z to the current time
Done generating
{"avgTemp": 31.5, "endTime": "2020-10-16T10:07:41.580722", "maxTemp": 49, "minTemp": 12, "startTime": "2020-10-16T10:06:21.580632"}