Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

How to Communicate with AWS IoT


Microvisor is ready to work with Amazon Web Services' AWS IoT offering, which uses MQTT as its messaging protocol. This guide will show you how to configure AWS IoT to receive data from a Microvisor-enabled device, and what you need to add to your own Microvisor-backed application to authenticate with AWS IoT.

It is not a guide to using Microvisor's MQTT functionality. Please see How to Issue MQTT Requests Under Microvisor if that's your need. Additionally, AWS IoT has some differences with a standard MQTT implementation. You can review these differences in the AWS IoT documentation(link takes you to an external page).

(warning)

Warning

This guide assumes you have already set up an account with AWS. If you have not done so, you can create an AWS account here(link takes you to an external page).

(warning)

Warning

Please be aware that outside of free tier usage, AWS IoT is a billable service. AWS charges for device connections, messaging, and other services. Please familiarize yourself with AWS IoT pricing(link takes you to an external page) before you continue.


Set up AWS IoT

set-up-aws-iot page anchor

Registering your Microvisor-enabled device with AWS IoT and preparing AWS to interact with it involves four key steps:

  1. Create an access policy for your device.
  2. Create an AWS IoT thing to represent your device.
  3. Create a client certificate and signing keys.
  4. Bind the policy to the certificate, and the certificate to the thing.

In this guide's workflow, steps 2-4 are embodied in a single step, below.

1. Create an access policy

1-create-an-access-policy page anchor
  1. Go to the AWS Console(link takes you to an external page) and then select Internet of Things > IoT Core from the Services icon.
  2. Navigate to Manage > Security > Policy .
  3. Click the Create policy button.
  4. Give the policy a name.
  5. Under Policy document , click on the JSON tab.
  6. In the boilerplate policy, set the values of the Action and Resource keys to "*" .
  7. Click Create .
(error)

Danger

The access policy set in the above step provides access to all AWS IoT resources and allows all actions. This is for simplicity, but in production you should limit devices to the actions and resources they need and no more. You may wish to do so in development too. We'll provide more tightly controlled policy later.

2. Create a thing on AWS IoT

2-create-a-thing-on-aws-iot page anchor
  1. Navigate to Manage > All devices > Things .
  2. Click the Create things button.
  3. Make sure Create single thing is selected and click Next .
  4. For the thing's name enter your device's SID. Now click Next .
  5. Make sure Auto-generate a new certificate (recommended) is selected and click Next .
  6. Select the policy you created in Step 1, above , and click Create thing .
  7. AWS will allow you to download all the certificates and keys. Do so now — you will use these later.

On the AWS IoT Console, navigate to Settings. Copy the Device data endpoint. Use this value as your MQTT broker hostname, which you will upload in a moment.

AWS IoT's MQTT port is 8883.


Provision the application

provision-the-application page anchor

The next phase involves transferring the client certificate, its private key, and the CA certificate to the device. This is a three-step process:

  1. Convert each secret to the supported format (DER).
  2. Upload each secret to secure storage in the Microvisor cloud.
  3. The application requests the secrets from the Microvisor cloud.

Each of the authentication files you downloaded from AWS IoT must be converted to the DER format. You use the openssl command to achieve this. For the certificates, use this form:


_10
openssl x509 -inform pem -in <CA_CERT_FILE_NAME> -outform der -out AmazonRootCA1.der
_10
openssl x509 -inform pem -in <CLIENT_CERT_FILE_NAME> -outform der -out ${MV_DEVICE_SID}-cert.der

To convert the private key, use:


_10
openssl pkcs8 -topk8 -in <PRIVATE_KEY_FILE_NAME> -inform pem -out ${MV_DEVICE_SID}-private_key.der -outform der -nocrypt

${MV_DEVICE_SID} is a shell environment variable that holds your device's SID. You will have set this if you have followed the Get Started with the Microvisor Nucleo Development Board guide. If not run this:


_10
export MV_DEVICE_SID=<YOUR_DEVICE_SID>

Secrets are uploaded on a file by file basis using the Microvisor REST API. At this time, you need to use a command line tool like curl to perform this task. The Twilio CLI will gain this functionality shortly.


_14
curl -X POST -s https://microvisor.twilio.com/v1/Devices/${MV_DEVICE_SID}/Configs \
_14
-d "Key=cert" \
_14
-d "Value=$(hexdump -v -e '1/1 "%02x"' ${MV_DEVICE_SID}-cert.der)" \
_14
-u ${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}
_14
_14
curl -X POST -s https://microvisor.twilio.com/v1/Devices/${MV_DEVICE_SID}/Configs \
_14
-d "Key=root-CA" \
_14
-d "Value=$(hexdump -v -e '1/1 "%02x"' AmazonRootCA1.der)" \
_14
-u ${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}
_14
_14
curl -X POST -s https://microvisor.twilio.com/v1/Devices/${MV_DEVICE_SID}/Secrets \
_14
-d "Key=private_key" \
_14
-d "Value=$(hexdump -v -e '1/1 "%02x"' ${MV_DEVICE_SID}-private_key.der)" \
_14
-u ${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}

Additionally, we recommend treating the MQTT broker and port as secrets. This saves you from the need to bake them into your app:


_10
curl -X POST -s https://microvisor.twilio.com/v1/Devices/${MV_DEVICE_SID}/Configs \
_10
-d "Key=broker-host" \
_10
-d "Value=<YOUR_AWS_DEVICE_DATA_ENDPOINT>" \
_10
-u ${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}
_10
_10
curl -X POST -s https://microvisor.twilio.com/v1/Devices/${MV_DEVICE_SID}/Configs \
_10
-d "Key=broker-port" \
_10
-d "Value=<YOUR_AWS_BROKER_PORT>" \
_10
-u ${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}

Each of the uploaded secrets is now available to be accessed by your application using Microvisor system calls. To do so, your code must first establish a network connection and then open a data channel of type MV_CHANNELTYPE_CONFIGFETCH. You can now retrieve the required secrets:


_57
uint8_t deviceid[35] = {0};
_57
mvGetDeviceId(deviceid, 34);
_57
_57
struct MvConfigKeyToFetch root_ca = {
_57
.scope = MV_CONFIGKEYFETCHSCOPE_DEVICE;
_57
.store = MV_CONFIGKEYFETCHSTORE_CONFIG;
_57
.key = {
_57
.data = (uint8_t*)"root-CA",
_57
.length = 7
_57
}
_57
};
_57
_57
MvConfigKeyToFetch cert = {
_57
.scope = MV_CONFIGKEYFETCHSCOPE_DEVICE;
_57
.store = MV_CONFIGKEYFETCHSTORE_CONFIG;
_57
.key = {
_57
.data = (uint8_t*)"cert",
_57
.length = 4
_57
}
_57
};
_57
_57
MvConfigKeyToFetch private_key = {
_57
.scope = MV_CONFIGKEYFETCHSCOPE_DEVICE;
_57
.store = MV_CONFIGKEYFETCHSTORE_CONFIG;
_57
.key = {
_57
.data = (uint8_t*)"private_key",
_57
.length = 11
_57
}
_57
};
_57
_57
MvConfigKeyToFetch broker_host = {
_57
.scope = MV_CONFIGKEYFETCHSCOPE_DEVICE;
_57
.store = MV_CONFIGKEYFETCHSTORE_CONFIG;
_57
.key = {
_57
.data = (uint8_t*)"broker-host",
_57
.length = 11
_57
}
_57
};
_57
_57
MvConfigKeyToFetch broker_port = {
_57
.scope = MV_CONFIGKEYFETCHSCOPE_DEVICE;
_57
.store = MV_CONFIGKEYFETCHSTORE_CONFIG;
_57
.key = {
_57
.data = (uint8_t*)"broker-port",
_57
.length = 11
_57
}
_57
};
_57
_57
MvConfigKeyToFetch keys[5] = {root_ca, cert, private_key, broker_host, broker_port};
_57
_57
MvConfigKeyFetchParams params = {
_57
.num_items = 5;
_57
.keys_to_fetch = keys
_57
};
_57
_57
enum MvStatus status = mvSendConfigFetchRequest(fetch_channel_handle, &params);
_57
assert(status == MV_STATUS_OK);

(information)

Info

You can find the fully working code from which these snippets were taken in our MQTT demo repo(link takes you to an external page).

The requested secrets will be retrieved asynchronously and their availability to the application signaled by a notification of type MV_EVENTTYPE_CHANNELDATAREADABLE to the fetch channel's notification center. Call mvReadConfigFetchResponseData() to retrieve the bulk data, and then call mvReadConfigResponseItem() for each specific item:


_66
MvConfigResponseData response;
_66
enum MvStatus status = mvReadConfigFetchResponseData(fetch_channel_handle, &response);
_66
assert(status == MV_STATUS_OK);
_66
_66
if (response.result != MV_CONFIGFETCHRESULT_OK) {
_66
server_error("Could not fetch config data (status: %d)", response.result);
_66
return;
_66
}
_66
_66
if (response.num_items != 5) {
_66
server_error("Could not get all items (retrieved %d items)", response.num_items);
_66
return;
_66
}
_66
_66
MvConfigKeyFetchResult result;
_66
uint8_t read_buffer[3072] __attribute__ ((aligned(512))) = {0};
_66
uint32_t read_buffer_used = 0;
_66
_66
struct SizedStringBuffer buf = {
_66
.data = read_buffer,
_66
.size = sizeof(read_buffer)
_66
.length = &read_buffer_used
_66
};
_66
_66
MvConfigResponseReadItemParams params = {
_66
.item_index = 0,
_66
.result = &result,
_66
.buf = &buf;
_66
};
_66
_66
status = mvReadConfigResponseItem(fetch_channel_handle, &params);
_66
assert(status == MV_STATUS_OK && result == MV_CONFIGFETCHRESULT_OK);
_66
// NOTE The function 'consume_binary()' converts the API-submitted string to binary data
_66
// It is not included here: please see the MQTT demo repo for details
_66
if (!consume_binary(root_ca, &root_ca_len, read_buffer, &read_buffer_used) {
_66
server_error("Could not unpack root CA");
_66
return;
_66
}
_66
_66
params.item_index = 1;
_66
status = mvReadConfigResponseItem(fetch_channel_handle, &params);
_66
assert(status == MV_STATUS_OK && result == MV_CONFIGFETCHRESULT_OK);
_66
if (!consume_binary(client_cert, &client_cert_len, read_buffer, &read_buffer_used) {
_66
server_error("Could not unpack client cert");
_66
return;
_66
}
_66
_66
params.item_index = 2;
_66
status = mvReadConfigResponseItem(fetch_channel_handle, &params);
_66
assert(status == MV_STATUS_OK && result == MV_CONFIGFETCHRESULT_OK);
_66
if (!consume_binary(private_key, &private_key_len, read_buffer, &read_buffer_used) {
_66
server_error("Could not unpack private key");
_66
return;
_66
}
_66
_66
params.item_index = 3;
_66
status = mvReadConfigResponseItem(fetch_channel_handle, &params);
_66
assert(status == MV_STATUS_OK && result == MV_CONFIGFETCHRESULT_OK);
_66
memcpy(mqtt_broker_host, read_buffer, read_buffer_used);
_66
broker_host_len = mqtt_broker_host_len;
_66
_66
params.item_index = 4;
_66
status = mvReadConfigResponseItem(fetch_channel_handle, &params);
_66
assert(status == MV_STATUS_OK && result == MV_CONFIGFETCHRESULT_OK);
_66
read_buffer[read_buffer_used] = '\0';
_66
mqtt_broker_port = strtol((const char *)read_buffer, NULL, 10);

With the secrets loaded, they can now be referenced when you establish a second network data channel, this time of type MV_CHANNELTYPE_MQTT. You can close down your fetch channel now.


_10
enum MvStatus status = mvCloseChannel(&fetch_channel_handle);


Rather than repeat the basics of Microvisor MQTT usage — for which see How to Issue MQTT Requests Under Microvisor — we'll focus on the key settings you need for AWS IoT usage.

When you connect to the AWS IoT broker, make sure your connection parameters set MV_MQTTPROTOCOLVERSION_V5 as the value of the MvMqttConnectRequest property protocol_version. AWS IoT uses MQTT 5.

The MvMqttConnectRequest properties tls_credentials and authentication will be set as follows:


_64
struct MvMqttAuthentication authentication = {
_64
.method = MV_MQTTAUTHENTICATIONMETHOD_NONE,
_64
.username_password = {
_64
.username = {NULL, 0},
_64
.password = {NULL, 0}
_64
}
_64
};
_64
_64
struct MvSizedString device_certs[] = {
_64
{
_64
.data = (uint8_t *)cert,
_64
.length = cert_len
_64
},
_64
};
_64
_64
struct MvTlsCertificateChain device_certificate = {
_64
.num_certs = 1,
_64
.certs = device_certs
_64
};
_64
_64
struct MvSizedString key = {
_64
.data = (uint8_t *)private_key,
_64
.length = private_key_len
_64
};
_64
_64
struct MvOwnTlsCertificateChain device_credentials = {
_64
.chain = device_certificate,
_64
.key = key
_64
};
_64
_64
struct MvSizedString ca_certs[] = {
_64
{
_64
.data = (uint8_t *)root_ca,
_64
.length = root_ca_len
_64
},
_64
};
_64
_64
struct MvTlsCertificateChain server_ca_certificate = {
_64
.num_certs = 1,
_64
.certs = ca_certs
_64
};
_64
_64
struct MvTlsCredentials tls_credentials = {
_64
.cacert = server_ca_certificate,
_64
.clientcert = device_credentials,
_64
};
_64
_64
struct MvMqttConnectRequest request = {
_64
.protocol_version = MV_MQTTPROTOCOLVERSION_V5,
_64
.host = {
_64
.data = broker_host,
_64
.length = broker_host_len
_64
},
_64
.port = broker_port,
_64
.clientid = {
_64
.data = client,
_64
.length = client_len
_64
},
_64
.authentication = authentication,
_64
.tls_credentials = &tls_credentials,
_64
.keepalive = 60,
_64
.clean_start = 0,
_64
.will = NULL,
_64
};

Issue the request with:


_10
enum MvStatus status = mvMqttRequestConnect(mqtt_channel_handle, &request);

If the returned value of status is MV_STATUS_OKAY, your MQTT channel's notification center will shortly receive a notification of type MV_EVENTTYPE_CHANNELDATAREADABLE. Call mvMqttGetNextReadableDataType() and check the returned data type. This value will be MV_MQTTREADABLEDATATYPE_CONNECTRESPONSE if the response is the result of your connection attempt. In this case, call mvMqttReadConnectResponse() and Microvisor will populate the MvMqttConnectResponse you pass into the system call:


_10
struct MvMqttConnectResponse {
_10
enum MvMqttRequestState request_state,
_10
uint32_t reason_code
_10
}

If the value of request_state is MV_MQTTCONNECTSTATUS_REQUESTCOMPLETED then your application has successfully connected to the AWS IoT MQTT broker, and you can subscribe to channels and then begin publishing messages to them.

If you receive another value, check the status it indicates. You should check you have downloaded, converted, and uploaded the correct CA and client certificates, and the client private key. Check they are being stored correctly and are accessible by your MQTT connection code. Make sure you are passing the certificates to AWS IoT correctly.


Tighten your AWS access policy

tighten-your-aws-access-policy page anchor

Once the device is connected to the AWS IoT broker, one of the actions it can take is to start publishing messages, subscribing to topics, and receiving messages from those topics. This is standard MQTT functionality so, again, we won't cover the process here. For AWS IoT, you must ensure that the topic to which you will be publishing is correctly enabled in your policy. At the start of this guide, we set an 'allow all' policy for convenience, but we really should implement one that's more secure and targets your device.

  1. Go to the AWS Console(link takes you to an external page) and then select Internet of Things > IoT Core from the Services icon.
  2. Navigate to Manage > Security > Policy .
  3. Click on your existing policy's name.
  4. Click on Edit active version .
  5. Click on JSON .
  6. Copy the following Statement object and paste it on over the existing one:


    _28
    "Statement": [
    _28
    {
    _28
    Effect": "Allow",
    _28
    "Action": "iot:Connect",
    _28
    "Resource": "arn:aws:iot:<region>:<account_id>:client/${iot:Connection.Thing.ThingName}"
    _28
    },
    _28
    {
    _28
    "Effect": "Allow",
    _28
    "Action": "iot:Publish",
    _28
    "Resource": "arn:aws:iot:<region>:<account_id>:topic/sensor/device/${iot:Connection.Thing.ThingName}"
    _28
    },
    _28
    {
    _28
    "Effect": "Allow",
    _28
    "Action": "iot:Subscribe",
    _28
    "Resource": [
    _28
    "arn:aws:iot:<region>:<account_id>:topicfilter/command/device/${iot:Connection.Thing.ThingName}",
    _28
    "arn:aws:iot:<region>:<account_id>:topicfilter/command/device/all"
    _28
    ]
    _28
    },
    _28
    {
    _28
    "Effect": "Allow",
    _28
    "Action": "iot:Receive",
    _28
    "Resource": [
    _28
    "arn:aws:iot:<region>:<account_id>:topic/command/device/${iot:Connection.Thing.ThingName}",
    _28
    "arn:aws:iot:<region>:<account_id>:topic/command/device/all"
    _28
    ]
    _28
    }
    _28
    ]

  7. Replace all instances of <region> with your own AWS region, and all instances of <account_id> with your own AWS account ID (numbers only; omit any hyphens).
  8. Check the JSON is valid: the AWS console will warn you of structural errors. Look for mis-paired array and object markers.
  9. Make sure Set the edited version as the active version for this policy is checked.
  10. Click the Save as new version button.

This policy is configured for the MQTT demo code. It includes the topic sensor/device/<DEVICE_SID> for message posting, and the topic command/device/<DEVICE_SID> for receiving messages. It also assumes your AWS IoT Thing's name matches your device's SID.

(information)

Info

Microvisor Help and Support

We welcome all inquiries you may have about Microvisor and its implementation, and any support questions that arise once you've begun developing with Microvisor. Please submit your queries via a KORE Wireless ticket: log in to the Kore console(link takes you to an external page) and click the Contact Support button in the left-hand navbar.


Rate this page: