コード例 #1
0
    async def send_message(self, message):
        """Sends a message to the default events endpoint on the Azure IoT Hub or Azure IoT Edge Hub instance.

        If the connection to the service has not previously been opened by a call to connect, this
        function will open the connection before sending the event.

        :param message: The actual message to send. Anything passed that is not an instance of the
            Message class will be converted to Message object.
        :type message: :class:`azure.iot.device.Message` or str

        :raises: :class:`azure.iot.device.exceptions.CredentialError` if credentials are invalid
            and a connection cannot be established.
        :raises: :class:`azure.iot.device.exceptions.ConnectionFailedError` if a establishing a
            connection results in failure.
        :raises: :class:`azure.iot.device.exceptions.ConnectionDroppedError` if connection is lost
            during execution.
        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.
        """
        if not isinstance(message, Message):
            message = Message(message)

        logger.info("Sending message to Hub...")
        send_message_async = async_adapter.emulate_async(
            self._iothub_pipeline.send_message)

        callback = async_adapter.AwaitableCallback()
        await send_message_async(message, callback=callback)
        await handle_result(callback)

        logger.info("Successfully sent message to Hub")
コード例 #2
0
    async def patch_twin_reported_properties(self, reported_properties_patch):
        """
        Update reported properties with the Azure IoT Hub or Azure IoT Edge Hub service.

        If the service returns an error on the patch operation, this function will raise the
        appropriate error.

        :param reported_properties_patch: Twin Reported Properties patch as a JSON dict
        :type reported_properties_patch: dict

        :raises: :class:`azure.iot.device.exceptions.CredentialError` if credentials are invalid
            and a connection cannot be established.
        :raises: :class:`azure.iot.device.exceptions.ConnectionFailedError` if a establishing a
            connection results in failure.
        :raises: :class:`azure.iot.device.exceptions.ConnectionDroppedError` if connection is lost
            during execution.
        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.
        """
        logger.info("Patching twin reported properties")

        if not self._iothub_pipeline.feature_enabled[constant.TWIN]:
            await self._enable_feature(constant.TWIN)

        patch_twin_async = async_adapter.emulate_async(
            self._iothub_pipeline.patch_twin_reported_properties)

        callback = async_adapter.AwaitableCallback()
        await patch_twin_async(patch=reported_properties_patch,
                               callback=callback)
        await handle_result(callback)

        logger.info("Successfully sent twin patch")
コード例 #3
0
    async def register(self):
        """
        Register the device with the provisioning service.

        Before returning the client will also disconnect from the provisioning service.
        If a registration attempt is made while a previous registration is in progress it may
        throw an error.

        :returns: RegistrationResult indicating the result of the registration.
        :rtype: :class:`azure.iot.device.RegistrationResult`

        :raises: :class:`azure.iot.device.exceptions.CredentialError` if credentials are invalid
            and a connection cannot be established.
        :raises: :class:`azure.iot.device.exceptions.ConnectionFailedError` if a establishing a
            connection results in failure.
        :raises: :class:`azure.iot.device.exceptions.ConnectionDroppedError` if connection is lost
            during execution.
        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.

        """
        logger.info("Registering with Provisioning Service...")

        if not self._pipeline.responses_enabled[dps_constant.REGISTER]:
            await self._enable_responses()

        register_async = async_adapter.emulate_async(self._pipeline.register)

        register_complete = async_adapter.AwaitableCallback(return_arg_name="result")
        await register_async(payload=self._provisioning_payload, callback=register_complete)
        result = await handle_result(register_complete)

        log_on_register_complete(result)
        return result
コード例 #4
0
    async def invoke_method(self, method_params, device_id, module_id=None):
        """Invoke a method from your client onto a device or module client, and receive the response to the method call.

        :param dict method_params: Should contain a methodName (str), payload (str),
            connectTimeoutInSeconds (int), responseTimeoutInSeconds (int).
        :param str device_id: Device ID of the target device where the method will be invoked.
        :param str module_id: Module ID of the target module where the method will be invoked. (Optional)

        :returns: method_result should contain a status, and a payload
        :rtype: dict
        """
        logger.info("Invoking {} method on {}{}".format(
            method_params["methodName"], device_id, module_id))

        invoke_method_async = async_adapter.emulate_async(
            self._http_pipeline.invoke_method)
        callback = async_adapter.AwaitableCallback(
            return_arg_name="invoke_method_response")
        await invoke_method_async(device_id,
                                  method_params,
                                  callback=callback,
                                  module_id=module_id)

        method_response = await handle_result(callback)
        logger.info("Successfully invoked method")
        return method_response
コード例 #5
0
 async def test_calling_object_calls_input_function_and_returns_result(
         self, mocker, mock_function):
     callback = async_adapter.AwaitableCallback(mock_function)
     result = callback()
     assert mock_function.call_count == 1
     assert mock_function.call_args == mocker.call()
     assert result == mock_function.return_value
コード例 #6
0
    async def update_sastoken(self, sastoken):
        """
        Update the client's SAS Token used for authentication, then reauthorizes the connection.

        This API can only be used if the client was initially created with a SAS Token.
        Note also that this API may return before the reauthorization/reconnection is completed.
        This means that some errors that may occur as part of the reconnection could occur in the
        background, and will not be raised by this method.

        :param str sastoken: The new SAS Token string for the client to use

        :raises: :class:`azure.iot.device.exceptions.ClientError` if the client was not initially
            created with a SAS token.
        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.
        :raises: ValueError if the sastoken parameter is invalid
        """
        self._replace_user_supplied_sastoken(sastoken)

        # Reauthorize the connection
        logger.info("Reauthorizing connection with Hub...")
        reauth_connection_async = async_adapter.emulate_async(
            self._mqtt_pipeline.reauthorize_connection
        )
        callback = async_adapter.AwaitableCallback()
        await reauth_connection_async(callback=callback)
        await handle_result(callback)
        # NOTE: Currently due to the MQTT3 implemenation, the pipeline reauthorization will return
        # after the disconnect. It does not wait for the reconnect to complete. This means that
        # any errors that may occur as part of the connect will not return via this callback.
        # They will instead go to the background exception handler.

        logger.info("Successfully reauthorized connection to Hub")
コード例 #7
0
 async def test_can_be_called_using_positional_args(self, mocker,
                                                    mock_function):
     callback = async_adapter.AwaitableCallback(mock_function)
     result = callback(1, 2, 3)
     assert mock_function.call_count == 1
     assert mock_function.call_args == mocker.call(1, 2, 3)
     assert result == mock_function.return_value
コード例 #8
0
    async def get_twin(self):
        """
        Gets the device or module twin from the Azure IoT Hub or Azure IoT Edge Hub service.

        :returns: Complete Twin as a JSON dict
        :rtype: dict

        :raises: :class:`azure.iot.device.exceptions.CredentialError` if credentials are invalid
            and a connection cannot be established.
        :raises: :class:`azure.iot.device.exceptions.ConnectionFailedError` if a establishing a
            connection results in failure.
        :raises: :class:`azure.iot.device.exceptions.ConnectionDroppedError` if connection is lost
            during execution.
        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.
        """
        logger.info("Getting twin")

        if not self._iothub_pipeline.feature_enabled[constant.TWIN]:
            await self._enable_feature(constant.TWIN)

        get_twin_async = async_adapter.emulate_async(
            self._iothub_pipeline.get_twin)

        callback = async_adapter.AwaitableCallback(return_arg_name="twin")
        await get_twin_async(callback=callback)
        twin = await handle_result(callback)
        logger.info("Successfully retrieved twin")
        return twin
コード例 #9
0
 async def test_can_be_called_using_explicit_kwargs(self, mocker,
                                                    mock_function):
     callback = async_adapter.AwaitableCallback(mock_function)
     result = callback(a=1, b=2, c=3)
     assert mock_function.call_count == 1
     assert mock_function.call_args == mocker.call(a=1, b=2, c=3)
     assert result == mock_function.return_value
    async def get_twin(self):
        """
        Gets the device or module twin from the Azure IoT Hub or Azure IoT Edge Hub service.

        :returns: Twin object which was retrieved from the hub
        """
        logger.info("Getting twin")

        if not self._pipeline.feature_enabled[constant.TWIN]:
            await self._enable_feature(constant.TWIN)

        get_twin_async = async_adapter.emulate_async(self._pipeline.get_twin)

        twin = None

        def sync_callback(received_twin):
            nonlocal twin
            logger.info("Successfully retrieved twin")
            twin = received_twin

        callback = async_adapter.AwaitableCallback(sync_callback)

        await get_twin_async(callback=callback)
        await callback.completion()

        return twin
コード例 #11
0
    async def send_message_to_output(self, message, output_name):
        """Sends an event/message to the given module output.

        These are outgoing events and are meant to be "output events"

        If the connection to the service has not previously been opened by a call to connect, this
        function will open the connection before sending the event.

        :param message: Message to send to the given output. Anything passed that is not an instance of the
            Message class will be converted to Message object.
        :param str output_name: Name of the output to send the event to.

        :raises: :class:`azure.iot.device.exceptions.CredentialError` if credentials are invalid
            and a connection cannot be established.
        :raises: :class:`azure.iot.device.exceptions.ConnectionFailedError` if a establishing a
            connection results in failure.
        :raises: :class:`azure.iot.device.exceptions.ConnectionDroppedError` if connection is lost
            during execution.
        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.
        """
        if not isinstance(message, Message):
            message = Message(message)

        message.output_name = output_name

        logger.info("Sending message to output:" + output_name + "...")
        send_output_event_async = async_adapter.emulate_async(
            self._iothub_pipeline.send_output_event)

        callback = async_adapter.AwaitableCallback()
        await send_output_event_async(message, callback=callback)
        await handle_result(callback)

        logger.info("Successfully sent message to output: " + output_name)
    async def patch_twin_reported_properties(self, reported_properties_patch):
        """
        Update reported properties with the Azure IoT Hub or Azure IoT Edge Hub service.

        If the service returns an error on the patch operation, this function will raise the
        appropriate error.

        :param reported_properties_patch:
        :type reported_properties_patch: dict, str, int, float, bool, or None (JSON compatible values)
        """
        logger.info("Patching twin reported properties")

        if not self._pipeline.feature_enabled[constant.TWIN]:
            await self._enable_feature(constant.TWIN)

        patch_twin_async = async_adapter.emulate_async(
            self._pipeline.patch_twin_reported_properties)

        def sync_callback():
            logger.info("Successfully sent twin patch")

        callback = async_adapter.AwaitableCallback(sync_callback)

        await patch_twin_async(patch=reported_properties_patch,
                               callback=callback)
        await callback.completion()
    async def send_to_output(self, message, output_name):
        """Sends an event/message to the given module output.

        These are outgoing events and are meant to be "output events"

        If the connection to the service has not previously been opened by a call to connect, this
        function will open the connection before sending the event.

        :param message: message to send to the given output. Anything passed that is not an instance of the
        Message class will be converted to Message object.
        :param output_name: Name of the output to send the event to.
        """
        if not isinstance(message, Message):
            message = Message(message)

        message.output_name = output_name

        logger.info("Sending message to output:" + output_name + "...")
        send_output_event_async = async_adapter.emulate_async(
            self._pipeline.send_output_event)

        def sync_callback():
            logger.info("Successfully sent message to output: " + output_name)

        callback = async_adapter.AwaitableCallback(sync_callback)

        await send_output_event_async(message, callback=callback)
        await callback.completion()
コード例 #14
0
    async def send_method_response(self, method_response):
        """Send a response to a method request via the Azure IoT Hub or Azure IoT Edge Hub.

        If the connection to the service has not previously been opened by a call to connect, this
        function will open the connection before sending the event.

        :param method_response: The MethodResponse to send
        :type method_response: :class:`azure.iot.device.MethodResponse`

        :raises: :class:`azure.iot.device.exceptions.CredentialError` if credentials are invalid
            and a connection cannot be established.
        :raises: :class:`azure.iot.device.exceptions.ConnectionFailedError` if a establishing a
            connection results in failure.
        :raises: :class:`azure.iot.device.exceptions.ConnectionDroppedError` if connection is lost
            during execution.
        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.
        """
        logger.info("Sending method response to Hub...")
        send_method_response_async = async_adapter.emulate_async(
            self._iothub_pipeline.send_method_response)

        callback = async_adapter.AwaitableCallback()

        # TODO: maybe consolidate method_request, result and status into a new object
        await send_method_response_async(method_response, callback=callback)
        await handle_result(callback)

        logger.info("Successfully sent method response to Hub")
コード例 #15
0
    async def disconnect(self):
        """Disconnect the client from the Azure IoT Hub or Azure IoT Edge Hub instance.

        It is recommended that you make sure to call this coroutine when you are completely done
        with the your client instance.

        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.
        """
        logger.info("Disconnecting from Hub...")

        logger.debug("Executing initial disconnect")
        disconnect_async = async_adapter.emulate_async(
            self._mqtt_pipeline.disconnect)
        callback = async_adapter.AwaitableCallback()
        await disconnect_async(callback=callback)
        await handle_result(callback)
        logger.debug("Successfully executed initial disconnect")

        # Note that in the process of stopping the handlers and resolving pending calls
        # a user-supplied handler may cause a reconnection to occur
        logger.debug("Stopping handlers...")
        self._handler_manager.stop(receiver_handlers_only=True)
        logger.debug("Successfully stopped handlers")

        # Disconnect again to ensure disconnection has ocurred due to the issue mentioned above
        logger.debug("Executing secondary disconnect...")
        disconnect_async = async_adapter.emulate_async(
            self._mqtt_pipeline.disconnect)
        callback = async_adapter.AwaitableCallback()
        await disconnect_async(callback=callback)
        await handle_result(callback)
        logger.debug("Successfully executed secondary disconnect")

        # It's also possible that in the (very short) time between stopping the handlers and
        # the second disconnect, additional items were received (e.g. C2D Message)
        # Currently, this isn't really possible to accurately check due to a
        # race condition / thread timing issue with inboxes where we can't guarantee how many
        # items are truly in them.
        # It has always been true of this client, even before handlers.
        #
        # However, even if the race condition is addressed, that will only allow us to log that
        # messages were lost. To actually fix the problem, IoTHub needs to support MQTT5 so that
        # we can unsubscribe from receiving data.

        logger.info("Successfully disconnected from Hub")
コード例 #16
0
 async def test_calling_object_completes_future(self):
     callback = async_adapter.AwaitableCallback()
     assert not callback.future.done()
     callback()
     await asyncio.sleep(0.1)  # wait to give time to complete the callback
     assert callback.future.done()
     assert not callback.future.exception()
     await callback.completion()
コード例 #17
0
 async def test_calling_object_completes_future_with_return_arg_name(
         self, fake_return_arg_value):
     callback = async_adapter.AwaitableCallback(return_arg_name="arg_name")
     assert not callback.future.done()
     callback(arg_name=fake_return_arg_value)
     await asyncio.sleep(0.1)  # wait to give time to complete the callback
     assert callback.future.done()
     assert not callback.future.exception()
     assert await callback.completion() == fake_return_arg_value
コード例 #18
0
 async def test_raises_error_with_return_arg_name(self, fake_error):
     callback = async_adapter.AwaitableCallback(return_arg_name="arg_name")
     assert not callback.future.done()
     callback(error=fake_error)
     await asyncio.sleep(0.1)  # wait to give time to complete the callback
     assert callback.future.done()
     assert callback.future.exception() == fake_error
     with pytest.raises(fake_error.__class__) as e_info:
         await callback.completion()
     assert e_info.value is fake_error
コード例 #19
0
    async def _enable_responses(self):
        """Enable to receive responses from Device Provisioning Service.
        """
        logger.info("Enabling reception of response from Device Provisioning Service...")
        subscribe_async = async_adapter.emulate_async(self._pipeline.enable_responses)

        subscription_complete = async_adapter.AwaitableCallback()
        await subscribe_async(callback=subscription_complete)
        await handle_result(subscription_complete)

        logger.info("Successfully subscribed to Device Provisioning Service to receive responses")
コード例 #20
0
 async def test_raises_error_without_return_arg_name(
         self, arbitrary_exception):
     callback = async_adapter.AwaitableCallback()
     assert not callback.future.done()
     callback(error=arbitrary_exception)
     await asyncio.sleep(0.1)  # wait to give time to complete the callback
     assert callback.future.done()
     assert callback.future.exception() == arbitrary_exception
     with pytest.raises(arbitrary_exception.__class__) as e_info:
         await callback.completion()
     assert e_info.value is arbitrary_exception
コード例 #21
0
    async def disconnect(self):
        """Disconnect the client from the Azure IoT Hub or Azure IoT Edge Hub instance.
        """
        logger.info("Disconnecting from Hub...")
        disconnect_async = async_adapter.emulate_async(self._iothub_pipeline.disconnect)

        callback = async_adapter.AwaitableCallback()
        await disconnect_async(callback=callback)
        await callback.completion()

        logger.info("Successfully disconnected from Hub")
コード例 #22
0
    async def connect(self):
        """Connects the client to an Azure IoT Hub or Azure IoT Edge Hub instance.

        The destination is chosen based on the credentials passed via the auth_provider parameter
        that was provided when this object was initialized.
        """
        logger.info("Connecting to Hub...")
        connect_async = async_adapter.emulate_async(self._iothub_pipeline.connect)

        callback = async_adapter.AwaitableCallback()
        await connect_async(callback=callback)
        await callback.completion()

        logger.info("Successfully connected to Hub")
コード例 #23
0
    async def _enable_feature(self, feature_name):
        """Enable an Azure IoT Hub feature

        :param feature_name: The name of the feature to enable.
        See azure.iot.device.common.pipeline.constant for possible values.
        """
        logger.info("Enabling feature:" + feature_name + "...")
        enable_feature_async = async_adapter.emulate_async(self._iothub_pipeline.enable_feature)

        callback = async_adapter.AwaitableCallback()
        await enable_feature_async(feature_name, callback=callback)
        await callback.completion()

        logger.info("Successfully enabled feature:" + feature_name)
コード例 #24
0
    async def disconnect(self):
        """Disconnect the client from the Azure IoT Hub or Azure IoT Edge Hub instance.

        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.
        """
        logger.info("Disconnecting from Hub...")
        disconnect_async = async_adapter.emulate_async(self._mqtt_pipeline.disconnect)

        callback = async_adapter.AwaitableCallback()
        await disconnect_async(callback=callback)
        await handle_result(callback)

        logger.info("Successfully disconnected from Hub")
コード例 #25
0
    async def register(self):
        """
        Register the device with the provisioning service.
        Before returning the client will also disconnect from the provisioning service.
        If a registration attempt is made while a previous registration is in progress it may throw an error.
        """
        logger.info("Registering with Provisioning Service...")
        register_async = async_adapter.emulate_async(self._polling_machine.register)

        callback = async_adapter.AwaitableCallback(return_arg_name="result")
        await register_async(callback=callback)
        result = await callback.completion()

        log_on_register_complete(result)
        return result
コード例 #26
0
    async def cancel(self):
        """
        Before returning the client will also disconnect from the provisioning service.

        In case there is no registration in process it will throw an error as there is
        no registration process to cancel.
        """
        logger.info("Disconnecting from Provisioning Service...")
        cancel_async = async_adapter.emulate_async(self._polling_machine.cancel)

        callback = async_adapter.AwaitableCallback()
        await cancel_async(callback=callback)
        await callback.completion()

        logger.info("Successfully cancelled the current registration process")
コード例 #27
0
    async def _enable_feature(self, feature_name):
        """Enable an Azure IoT Hub feature in the transport

        :param feature_name: The name of the feature to enable.
        See azure.iot.device.common.transport.constant for possible values.
        """
        logger.info("Enabling feature:" + feature_name + "...")
        enable_feature_async = async_adapter.emulate_async(
            self._transport.enable_feature)

        def sync_callback():
            logger.info("Successfully enabled feature:" + feature_name)

        callback = async_adapter.AwaitableCallback(sync_callback)

        await enable_feature_async(feature_name, callback=callback)
コード例 #28
0
    async def get_storage_info_for_blob(self, blob_name):
        """Sends a POST request over HTTP to an IoTHub endpoint that will return information for uploading via the Azure Storage Account linked to the IoTHub your device is connected to.

        :param str blob_name: The name in string format of the blob that will be uploaded using the storage API. This name will be used to generate the proper credentials for Storage, and needs to match what will be used with the Azure Storage SDK to perform the blob upload.

        :returns: A JSON-like (dictionary) object from IoT Hub that will contain relevant information including: correlationId, hostName, containerName, blobName, sasToken.
        """
        get_storage_info_for_blob_async = async_adapter.emulate_async(
            self._http_pipeline.get_storage_info_for_blob
        )

        callback = async_adapter.AwaitableCallback(return_arg_name="storage_info")
        await get_storage_info_for_blob_async(blob_name=blob_name, callback=callback)
        storage_info = await handle_result(callback)
        logger.info("Successfully retrieved storage_info")
        return storage_info
コード例 #29
0
    async def send_message_to_output(self, message, output_name):
        """Sends an event/message to the given module output.

        These are outgoing events and are meant to be "output events"

        If the connection to the service has not previously been opened by a call to connect, this
        function will open the connection before sending the event.

        :param message: Message to send to the given output. Anything passed that is not an
            instance of the Message class will be converted to Message object.
        :type message: :class:`azure.iot.device.Message` or str
        :param str output_name: Name of the output to send the event to.

        :raises: :class:`azure.iot.device.exceptions.CredentialError` if credentials are invalid
            and a connection cannot be established.
        :raises: :class:`azure.iot.device.exceptions.ConnectionFailedError` if a establishing a
            connection results in failure.
        :raises: :class:`azure.iot.device.exceptions.ConnectionDroppedError` if connection is lost
            during execution.
        :raises: :class:`azure.iot.device.exceptions.OperationTimeout` if connection attempt
            times out
        :raises: :class:`azure.iot.device.exceptions.NoConnectionError` if the client is not
            connected (and there is no auto-connect enabled)
        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.
        :raises: ValueError if the message fails size validation.
        """
        if not isinstance(message, Message):
            message = Message(message)

        if message.get_size() > device_constant.TELEMETRY_MESSAGE_SIZE_LIMIT:
            raise ValueError("Size of message can not exceed 256 KB.")

        message.output_name = output_name

        logger.info("Sending message to output:" + output_name + "...")
        send_output_message_async = async_adapter.emulate_async(
            self._mqtt_pipeline.send_output_message
        )

        callback = async_adapter.AwaitableCallback()
        await send_output_message_async(message, callback=callback)
        await handle_result(callback)

        logger.info("Successfully sent message to output: " + output_name)
コード例 #30
0
    async def shutdown(self):
        """Shut down the client for graceful exit.

        Once this method is called, any attempts at further client calls will result in a
        ClientError being raised

        :raises: :class:`azure.iot.device.exceptions.ClientError` if there is an unexpected failure
            during execution.
        """
        logger.info("Initiating client shutdown")
        # Note that client disconnect does the following:
        #   - Disconnects the pipeline
        #   - Resolves all pending receiver handler calls
        #   - Stops receiver handler threads
        await self.disconnect()

        # Note that shutting down does the following:
        #   - Disconnects the MQTT pipeline
        #   - Stops MQTT pipeline threads
        logger.debug("Beginning pipeline shutdown operation")
        shutdown_async = async_adapter.emulate_async(
            self._mqtt_pipeline.shutdown)
        callback = async_adapter.AwaitableCallback()
        await shutdown_async(callback=callback)
        await handle_result(callback)
        logger.debug("Completed pipeline shutdown operation")

        # Stop the Client Event handlers now that everything else is completed
        self._handler_manager.stop(receiver_handlers_only=False)

        # Yes, that means the pipeline is disconnected twice (well, actually three times if you
        # consider that the client-level disconnect causes two pipeline-level disconnects for
        # reasons explained in comments in the client's .disconnect() method).
        #
        # This last disconnect that occurs as a result of the pipeline shutdown is a bit different
        # from the first though, in that it's more "final" and can't simply just be reconnected.

        # Note also that only the MQTT pipeline is shut down. The reason is twofold:
        #   1. There are no known issues related to graceful exit if the HTTP pipeline is not
        #      explicitly shut down
        #   2. The HTTP pipeline is planned for eventual removal from the client
        # In light of these two facts, it seemed irrelevant to spend time implementing shutdown
        # capability for HTTP pipeline.
        logger.info("Client shutdown complete")