зеркало из
1
0
Форкнуть 0
azure-iot-sdk-python/migration_guide_iothub.md

13 KiB

Azure IoT Device SDK for Python Migration Guide - IoTHubDeviceClient and IoTHubModuleClient

This guide details how to update existing code that uses an azure-iot-device V2 release to use a V3 release instead. While the APIs remain mostly the same, there are several differences you will need to account for in your application, as some APIs have changed, and we have removed some of the implicit behaviors present in V2 in order to provide a more reliable and consistent user experience.

Note that this guide mostly refers to the IoTHubDeviceClient, although it's contents apply equally to the IoTHubModuleClient.

For changes to the ProvisioningDeviceClient please refer to migration_guide_provisioning.md in this same directory.

Connecting to IoTHub

One of the primary changes in V3 is the removal of automatic connections when invoking other APIs on the IoTHubDeviceClient and IoTHubModuleClient. You must now make an explicit manual connection before sending or receiving any data.

V2

from azure.iot.device import IoTHubDeviceClient

client = IoTHubDeviceClient.create_from_connection_string("<Your Connection String>")
client.send_message("some message")

V3

from azure.iot.device import IoTHubDeviceClient

client = IoTHubDeviceClient.create_from_connection_string("<Your Connection String>")
client.connect()
client.send_message("some message")

Note that many people using V2 may already have been doing manual connects, as for some time, this has been our recommended practice.

Note also that this change does not affect automatic reconnection attempts in the case of network failure. Once the manual connect has been successful, the client will (under default settings) still attempt to retain that connected state as it did in V2.

Receiving data from IoTHub

Similarly to the above, there is an additional explicit step you must now make when trying to receive data. In addition to setting your handler, you must explicitly start/stop receiving. Note also that the above step of manually connecting must also be done before starting to receive data.

Furthermore, note that the content of the message is now referred to by the 'payload' attribute on the message, rather than the 'data' attribute (see "Message" section below)

V2

from azure.iot.device import IoTHubDeviceClient

client = IoTHubDeviceClient.create_from_connection_string("<Your Connection String>")

# define behavior for receiving a message
def message_handler(message):
    print("the data in the message received was ")
    print(message.data)
    print("custom properties are")
    print(message.custom_properties)

# set the message handler on the client
client.on_message_received = message_handler

V3

from azure.iot.device import IoTHubDeviceClient

client = IoTHubDeviceClient.create_from_connection_string("<Your Connection String>")

# define behavior for receiving a message
def message_handler(message):
    print("the payload of the message received was ")
    print(message.payload)
    print("custom properties are")
    print(message.custom_properties)

# set the message handler on the client
client.on_message_received = message_handler

# connect and start receiving messages
client.connect()
client.start_message_receive()

Note that this must be done not just for receiving messages, but receiving any data. Consult the chart below to see which APIs you will need for the type of data you are receiving.

Data Type Handler name Start Receive API Stop Receive API
Messages .on_message_received .start_message_receive() .stop_message_receive()
Method Requests .on_method_request_received .start_direct_method_request_receive() .stop_direct_method_request_receive()
Twin Desired Properties Patches .on_twin_desired_properties_patch_received .start_twin_desired_properties_patch_receive() .stop_twin_desired_properties_patch_receive()

Finally, it should be clarified that the following receive APIs that were deprecated in V2 have been fully removed in V3:

  • .receive_message()
  • .receive_message_on_input()
  • .receive_method_request()
  • .receive_twin_desired_properties_patch()

All receives should now be done using the handlers in the table above.

Direct Methods

For clarity, all references to direct methods are now explicit about being "direct methods", rather than the more generic (and overloaded) "method". As such, the following methods and objects have all had a name change:

  • .invoke_method() -> .invoke_direct_method()
  • MethodRequest -> DirectMethodRequest
  • MethodResponse -> DirectMethodResponse

Message object

Some changes have been made to the Message object used for sending and receiving data.

  • The .data attribute is now called .payload for consistency with other objects in the API
  • The message_id parameter is no longer part of the constructor arguments. It should be manually added as an attribute, just like all other attributes
  • The payload of a received Message is now a unicode string value instead of a bytestring value. It will be decoded according to the content encoding property sent along with the message.

V2

from azure.iot.device import Message

payload = "this is a payload"
message_id = "1234"
m = Message(data=payload, message_id=message_id)

assert m.data == payload
assert m.message_id = message_id

V3

from azure.iot.device import Message

payload = "this is a payload"
message_id = "1234"
m = Message(payload=payload)
m.message_id = message_id

assert m.payload == payload

Shutting down

While using the .shutdown() method when you are completely finished with an instance of the client has been a highly recommended practice for some time, some early versions of V2 did not require it. As of V3, in order to ensure a graceful exit, you must make an explicit shutdown.

V2

from azure.iot.device import IoTHubDeviceClient

client = IoTHubDeviceClient.create_from_connection_string("<Your Connection String>")

# ...
#<do things>
# ...

V3

from azure.iot.device import IoTHubDeviceClient

client = IoTHubDeviceClient.create_from_connection_string("<Your Connection String>")

# ...
#<do things>
# ...

client.shutdown()

Symmetric Key Authentication

Creating a client that uses a symmetric key to authenticate is now done via the new .create() factory method instead of .create_from_symmetric_key()

V2

from azure.iot.device import IoTHubDeviceClient

client = IoTHubDeviceClient.create_from_symmetric_key(
    symmetric_key="<Your Symmetric Key>",
    hostname="<Your Hostname>",
    device_id="<Your Device ID>"
)

V3

from azure.iot.device import IoTHubDeviceClient

client = IoTHubDeviceClient.create(
    symmetric_key="<Your Symmetric Key>",
    hostname="<Your Hostname>",
    device_id="<Your Device ID>"
)

Custom SAS Token Authentication

There have been significant changes surrounding this style of authentication - it was rather complex in V2, and we have tried to simplify it for V3. It now also uses the new .create() method rather than .create_from_sastoken(). With this new style of providing a custom token via callback, you no longer will have to manually update the SAS token via the .on_new_sastoken_required handler, and as such, the handler no longer exists.

V2

from azure.iot.device import IoTHubDeviceClient

def get_new_sastoken():
    sastoken = # Do something here to create/retrieve a token
    return sastoken

sastoken = get_new_sastoken()
client = IoTHubDeviceClient.create_from_sastoken(sastoken)

def sastoken_update_handler():
    print("Updating SAS Token...")
    sastoken = get_new_sastoken()
    client.update_sastoken(sastoken)
    print("SAS Token updated")

client.on_new_sastoken_required = sastoken_update_handler

V3

from azure.iot.device import IoTHubDeviceClient

def get_new_sastoken():
    sastoken = # Do something here to create/retrieve a token
    return sastoken

client = IoTHubDeviceClient.create(
    hostname="<Your Hostname>",
    device_id="<Your Device ID>",
    sastoken_fn=get_new_sastoken,
)

X509 Authentication

Using X509 authentication is now provided via the new ssl_context keyword for the .create() method, rather than having it's own .create_from_x509_certificate() method. This is to allow additional flexibility for customers who wish for more control over their TLS/SSL authorization. See "TLS/SSL customization" below for more information.

V2

from azure.iot.device import IoTHubDeviceClient, X509

x509 = X509(
    cert_file="<Your X509 Cert File Path>",
    key_file="<Your X509 Key File>",
    pass_phrase="<Your X509 Pass Phrase>",
)

client = IoTHubDeviceClient.create_from_x509_certificate(
    hostname="<Your IoTHub Hostname>",
    device_id="<Your Device ID>",
    x509=x509,
)

V3

from azure.iot.device import IoTHubDeviceClient
import ssl

ssl_context = ssl.SSLContext.create_default_context()
ssl_context.load_cert_chain(
    certfile="<Your X509 Cert File Path>",
    keyfile="<Your X509 Key File>",
    password="<Your X509 Pass Phrase>",
)

client = IoTHubDeviceClient.create(
    hostname="<Your IoTHub Hostname>",
    device_id="<Your Device ID>",
    ssl_context=ssl_context,
)

Note that SSLContexts can be used with the .create_from_connection_string() factory method as well, so V3 now fully supports X509 connection strings.

V3

from azure.iot.device import IoTHubDeviceClient
import ssl

ssl_context = ssl.SSLContext.create_default_context()
ssl_context.load_cert_chain(
    certfile="<Your X509 Cert File Path>",
    keyfile="<Your X509 Key File>",
    password="<Your X509 Pass Phrase>",
)

client = IoTHubDeviceClient.create_from_connection_string(
    "<Your X509 Connection String>",
    ssl_context=ssl_context,
)

TLS/SSL Customization

To allow users more flexibility, we have added the ability to inject an SSLContext object into the client via the optional ssl_context keyword argument to factory methods in order to customize the TLS/SSL encryption and authentication. As a result, some features previously handled via client APIs are now expected to have been directly set on the injected SSLContext.

By moving to a model that allows SSLContext injection we not only bring our client in line with standard practices, but we also allow for users to modify any aspect of their SSLContext, not just the ones we previously supported via API.

Server Verification Certificates (CA certs)

V2

from azure.iot.device import IoTHubDeviceClient

certfile = open("<Your CA Certificate File Path>")
root_ca_cert = certfile.read()

client = IoTHubDeviceClient.create_from_connection_string(
    "<Your Connection String>",
    server_verification_cert=root_ca_cert
)

V3

from azure.iot.device import IoTHubDeviceClient
import ssl

ssl_context = ssl.SSLContext.create_default_context(
    cafile="<Your CA Certificate File Path>",
)

client = IoTHubDeviceClient.create_from_connection_string(
    "<Your Connection String>",
    ssl_context=ssl_context,
)

Cipher Suites

V2

from azure.iot.device import IoTHubDeviceClient

client = IoTHubDeviceClient.create_from_connection_string(
    "<Your Connection String>",
    cipher="<Your Cipher>"
)

V3

from azure.iot.device import IoTHubDeviceClient
import ssl

ssl_context = ssl.SSLContext.create_default_context()
ssl_context.set_ciphers("<Your Cipher>")

client = IoTHubDeviceClient.create_from_connection_string(
    "<Your Connection String>",
    ssl_context=ssl_context,
)

Modified Client Options

Some keyword arguments provided at client creation have changed or been removed

V2 V3 Explanation
connection_retry auto_reconnect Improved clarity
connection_retry_interval REMOVED Automatic reconnect no longer uses a static interval
auto_connect REMOVED Initial manual connection now required
ensure_desired_properties REMOVED No more implicit twin updates
sastoken_ttl REMOVED Unnecessary, but open to re-adding if a use case emerges
gateway_hostname REMOVED Supported via hostname parameter
server_verification_cert REMOVED Supported via SSL injection
cipher REMOVED Supported via SSL injection