{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Multi Region IoT - Connect Device to AWS IoT\n", "\n", "After the ACM PCA setup has been created and a device certificate has been issued the device is now ready to connect to AWS IoT. \n", "\n", "Because of the JITR configuration for the PCA registered with AWS IoT the device will be provisioned upon the first connection attempt. That means the device certificate will get registered with AWS IoT, an IoT policy will be created if it does not exists, the policy will be attached to the certificate and the certificate will be attached to the device automatically.\n", "\n", "The value of the CN in the certificate will be used as thing name." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Libraries" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient\n", "from os.path import join\n", "import boto3\n", "import json\n", "import logging\n", "import time" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Note: If you get an error that the AWSIoTPythonSDK is not installed, install the SDK with the command below and import the libraries again!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install AWSIoTPythonSDK -t ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Function to Delete a Device\n", "The function deletes a device as well as the attached certificate. It does not delete the policy attached to the certificate." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def delete_thing(thing_name):\n", " print(\" DELETING {}\".format(thing_name))\n", "\n", " try:\n", " r_principals = c_iot.list_thing_principals(thingName=thing_name)\n", " except Exception as e:\n", " print(\"ERROR listing thing principals: {}\".format(e))\n", " r_principals = {'principals': []}\n", "\n", " #print(\"r_principals: {}\".format(r_principals))\n", " for arn in r_principals['principals']:\n", " cert_id = arn.split('/')[1]\n", " print(\" arn: {} cert_id: {}\".format(arn, cert_id))\n", "\n", " r_detach_thing = c_iot.detach_thing_principal(thingName=thing_name,principal=arn)\n", " print(\" DETACH THING: {}\".format(r_detach_thing))\n", "\n", " r_upd_cert = c_iot.update_certificate(certificateId=cert_id,newStatus='INACTIVE')\n", " print(\" INACTIVE: {}\".format(r_upd_cert))\n", "\n", " r_policies = c_iot.list_principal_policies(principal=arn)\n", " #print(\" r_policies: {}\".format(r_policies))\n", "\n", " for pol in r_policies['policies']:\n", " pol_name = pol['policyName']\n", " print(\" pol_name: {}\".format(pol_name))\n", " r_detach_pol = c_iot.detach_policy(policyName=pol_name,target=arn)\n", " print(\" DETACH POL: {}\".format(r_detach_pol))\n", "\n", " r_del_cert = c_iot.delete_certificate(certificateId=cert_id,forceDelete=True)\n", " print(\" DEL CERT: {}\".format(r_del_cert))\n", "\n", " r_del_thing = c_iot.delete_thing(thingName=thing_name)\n", " print(\" DELETE THING: {}\\n\".format(r_del_thing))\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Restore variable that have been defined in the notebook which has been used to create the ACM PCA setup." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%store -r config\n", "print(\"config: {}\".format(json.dumps(config, indent=4, default=str)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## IoT Endpoint\n", "To connect the device to AWS IoT we need to get the iot endpoint." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before the iot endpoint can be determined a boto3 client to AWS IoT must be created.\n", "\n", "The device will connect to the master region. After the device has been connected to the master region you can get the endpoint for the slave region and try to connect the device there as well." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "c_iot_m = boto3.client('iot', region_name = config['aws_region_master'])\n", "c_iot_s = boto3.client('iot', region_name = config['aws_region_slave'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Get the iot endpoint." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = c_iot_m.describe_endpoint(endpointType='iot:Data-ATS')\n", "\n", "iot_endpoint = response['endpointAddress']\n", "print(\"iot_endpoint: {}\".format(iot_endpoint))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Connect the Device\n", "Now connect the device to AWS IoT. When a device is reqistered automatically through JITP it will be disconnected automatically after the first connection attempt and the device is being registered. After the connection has timed out the code will wait some seconds and then connect again to AWS IoT. This could take a little bit, so please remain patient.\n", "\n", "### Before you start, go to the AWS IoT Console -> Test and subscribe to \"$aws/events/#\" and \"cmd/+/pca\"\n", "\n", "When a certificate is registered automatically with AWS IoT the service will publish a message to\n", "\n", "`$aws/events/certificates/registered/[certificateId]`\n", "\n", "If events for \"Thing: created, update, deleted\" are enbled in your AWS IoT settings AWS IoT will also publish a message to \n", "\n", "`$aws/events/thing/[clientId]/created`\n", "\n", "when the device is being created.\n", "\n", "Use the same thing name that you used to request a certificate in the previous notebook. Feel free to create more certificates and connect more things." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "thing_name = 'thing-mr04'\n", "\n", "root_ca = 'AmazonRootCA1.pem'\n", "\n", "device_key_file = '{}.device.key.pem'.format(thing_name)\n", "device_cert_file = '{}.device.cert.pem'.format(thing_name)\n", "\n", "# AWS IoT Python SDK needs logging\n", "logger = logging.getLogger(\"AWSIoTPythonSDK.core\")\n", "#logger.setLevel(logging.DEBUG)\n", "logger.setLevel(logging.INFO)\n", "streamHandler = logging.StreamHandler()\n", "formatter = logging.Formatter(\"[%(asctime)s - %(levelname)s - %(filename)s:%(lineno)s - %(funcName)s - %(message)s\")\n", "streamHandler.setFormatter(formatter)\n", "logger.addHandler(streamHandler)\n", "\n", "myAWSIoTMQTTClient = None\n", "myAWSIoTMQTTClient = AWSIoTMQTTClient(thing_name)\n", "myAWSIoTMQTTClient.configureEndpoint(iot_endpoint, 8883)\n", "myAWSIoTMQTTClient.configureCredentials(root_ca, \n", " join(config['PCA_directory'], device_key_file), \n", " join(config['PCA_directory'], device_cert_file))\n", "\n", "# AWSIoTMQTTClient connection configuration\n", "myAWSIoTMQTTClient.configureAutoReconnectBackoffTime(1, 32, 20)\n", "myAWSIoTMQTTClient.configureOfflinePublishQueueing(-1) # Infinite offline Publish queueing\n", "myAWSIoTMQTTClient.configureDrainingFrequency(2) # Draining: 2 Hz\n", "myAWSIoTMQTTClient.configureConnectDisconnectTimeout(10) # 10 sec\n", "myAWSIoTMQTTClient.configureMQTTOperationTimeout(5) # 5 sec\n", "\n", "# Connect and reconnect to AWS IoT\n", "try:\n", " myAWSIoTMQTTClient.connect()\n", "except Exception as e:\n", " logger.error('{}'.format(e))\n", " time.sleep(5)\n", " myAWSIoTMQTTClient.connect()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Verify\n", "\n", "Verify that the device has been created in the master and the slave region.\n", "\n", "### Master region" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = c_iot_m.describe_thing(thingName = thing_name)\n", "\n", "print(\"response: {}\".format(json.dumps(response, indent=4, default=str)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Slave region" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = c_iot_s.describe_thing(thingName = thing_name)\n", "\n", "print(\"response: {}\".format(json.dumps(response, indent=4, default=str)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Publish\n", "Publish a message in the master region to verify that the device works as expected.\n", "\n", "**You have subsribed to \"cmd/+/pca# in the master region?**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "topic = 'cmd/{}/pca'.format(thing_name)\n", "print(\"topic: {}\".format(topic))\n", "message = {\"provisioned\": \"through ACM PCA combined with JITR\", \"thing_name\": \"{}\".format(thing_name)}\n", "\n", "myAWSIoTMQTTClient.publish(topic, json.dumps(message), 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Disconnect the device." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "myAWSIoTMQTTClient.disconnect()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Delete the devices. **Note: the IoT policy created through JITP must be deleted manually**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "delete_thing('YOUR_THING_NAME_HERE')" ] } ], "metadata": { "kernelspec": { "display_name": "conda_python3", "language": "python", "name": "conda_python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }