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.
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.
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 before you continue.
Registering your Microvisor-enabled device with AWS IoT and preparing AWS to interact with it involves four key steps:
In this guide's workflow, steps 2-4 are embodied in a single step, below.
"*"
.
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.
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
.
The next phase involves transferring the client certificate, its private key, and the CA certificate to the device. This is a three-step process:
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:
1openssl x509 -inform pem -in <CA_CERT_FILE_NAME> -outform der -out AmazonRootCA1.der2openssl x509 -inform pem -in <CLIENT_CERT_FILE_NAME> -outform der -out ${MV_DEVICE_SID}-cert.der
To convert the private key, use:
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:
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.
1curl -X POST -s https://microvisor.twilio.com/v1/Devices/${MV_DEVICE_SID}/Configs \2-d "Key=cert" \3-d "Value=$(hexdump -v -e '1/1 "%02x"' ${MV_DEVICE_SID}-cert.der)" \4-u ${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}56curl -X POST -s https://microvisor.twilio.com/v1/Devices/${MV_DEVICE_SID}/Configs \7-d "Key=root-CA" \8-d "Value=$(hexdump -v -e '1/1 "%02x"' AmazonRootCA1.der)" \9-u ${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}1011curl -X POST -s https://microvisor.twilio.com/v1/Devices/${MV_DEVICE_SID}/Secrets \12-d "Key=private_key" \13-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:
1curl -X POST -s https://microvisor.twilio.com/v1/Devices/${MV_DEVICE_SID}/Configs \2-d "Key=broker-host" \3-d "Value=<YOUR_AWS_DEVICE_DATA_ENDPOINT>" \4-u ${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}56curl -X POST -s https://microvisor.twilio.com/v1/Devices/${MV_DEVICE_SID}/Configs \7-d "Key=broker-port" \8-d "Value=<YOUR_AWS_BROKER_PORT>" \9-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:
1uint8_t deviceid[35] = {0};2mvGetDeviceId(deviceid, 34);34struct MvConfigKeyToFetch root_ca = {5.scope = MV_CONFIGKEYFETCHSCOPE_DEVICE;6.store = MV_CONFIGKEYFETCHSTORE_CONFIG;7.key = {8.data = (uint8_t*)"root-CA",9.length = 710}11};1213MvConfigKeyToFetch cert = {14.scope = MV_CONFIGKEYFETCHSCOPE_DEVICE;15.store = MV_CONFIGKEYFETCHSTORE_CONFIG;16.key = {17.data = (uint8_t*)"cert",18.length = 419}20};2122MvConfigKeyToFetch private_key = {23.scope = MV_CONFIGKEYFETCHSCOPE_DEVICE;24.store = MV_CONFIGKEYFETCHSTORE_CONFIG;25.key = {26.data = (uint8_t*)"private_key",27.length = 1128}29};3031MvConfigKeyToFetch broker_host = {32.scope = MV_CONFIGKEYFETCHSCOPE_DEVICE;33.store = MV_CONFIGKEYFETCHSTORE_CONFIG;34.key = {35.data = (uint8_t*)"broker-host",36.length = 1137}38};3940MvConfigKeyToFetch broker_port = {41.scope = MV_CONFIGKEYFETCHSCOPE_DEVICE;42.store = MV_CONFIGKEYFETCHSTORE_CONFIG;43.key = {44.data = (uint8_t*)"broker-port",45.length = 1146}47};4849MvConfigKeyToFetch keys[5] = {root_ca, cert, private_key, broker_host, broker_port};5051MvConfigKeyFetchParams params = {52.num_items = 5;53.keys_to_fetch = keys54};5556enum MvStatus status = mvSendConfigFetchRequest(fetch_channel_handle, ¶ms);57assert(status == MV_STATUS_OK);
You can find the fully working code from which these snippets were taken in our MQTT demo repo.
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:
1MvConfigResponseData response;2enum MvStatus status = mvReadConfigFetchResponseData(fetch_channel_handle, &response);3assert(status == MV_STATUS_OK);45if (response.result != MV_CONFIGFETCHRESULT_OK) {6server_error("Could not fetch config data (status: %d)", response.result);7return;8}910if (response.num_items != 5) {11server_error("Could not get all items (retrieved %d items)", response.num_items);12return;13}1415MvConfigKeyFetchResult result;16uint8_t read_buffer[3072] __attribute__ ((aligned(512))) = {0};17uint32_t read_buffer_used = 0;1819struct SizedStringBuffer buf = {20.data = read_buffer,21.size = sizeof(read_buffer)22.length = &read_buffer_used23};2425MvConfigResponseReadItemParams params = {26.item_index = 0,27.result = &result,28.buf = &buf;29};3031status = mvReadConfigResponseItem(fetch_channel_handle, ¶ms);32assert(status == MV_STATUS_OK && result == MV_CONFIGFETCHRESULT_OK);33// NOTE The function 'consume_binary()' converts the API-submitted string to binary data34// It is not included here: please see the MQTT demo repo for details35if (!consume_binary(root_ca, &root_ca_len, read_buffer, &read_buffer_used) {36server_error("Could not unpack root CA");37return;38}3940params.item_index = 1;41status = mvReadConfigResponseItem(fetch_channel_handle, ¶ms);42assert(status == MV_STATUS_OK && result == MV_CONFIGFETCHRESULT_OK);43if (!consume_binary(client_cert, &client_cert_len, read_buffer, &read_buffer_used) {44server_error("Could not unpack client cert");45return;46}4748params.item_index = 2;49status = mvReadConfigResponseItem(fetch_channel_handle, ¶ms);50assert(status == MV_STATUS_OK && result == MV_CONFIGFETCHRESULT_OK);51if (!consume_binary(private_key, &private_key_len, read_buffer, &read_buffer_used) {52server_error("Could not unpack private key");53return;54}5556params.item_index = 3;57status = mvReadConfigResponseItem(fetch_channel_handle, ¶ms);58assert(status == MV_STATUS_OK && result == MV_CONFIGFETCHRESULT_OK);59memcpy(mqtt_broker_host, read_buffer, read_buffer_used);60broker_host_len = mqtt_broker_host_len;6162params.item_index = 4;63status = mvReadConfigResponseItem(fetch_channel_handle, ¶ms);64assert(status == MV_STATUS_OK && result == MV_CONFIGFETCHRESULT_OK);65read_buffer[read_buffer_used] = '\0';66mqtt_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.
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:
1struct MvMqttAuthentication authentication = {2.method = MV_MQTTAUTHENTICATIONMETHOD_NONE,3.username_password = {4.username = {NULL, 0},5.password = {NULL, 0}6}7};89struct MvSizedString device_certs[] = {10{11.data = (uint8_t *)cert,12.length = cert_len13},14};1516struct MvTlsCertificateChain device_certificate = {17.num_certs = 1,18.certs = device_certs19};2021struct MvSizedString key = {22.data = (uint8_t *)private_key,23.length = private_key_len24};2526struct MvOwnTlsCertificateChain device_credentials = {27.chain = device_certificate,28.key = key29};3031struct MvSizedString ca_certs[] = {32{33.data = (uint8_t *)root_ca,34.length = root_ca_len35},36};3738struct MvTlsCertificateChain server_ca_certificate = {39.num_certs = 1,40.certs = ca_certs41};4243struct MvTlsCredentials tls_credentials = {44.cacert = server_ca_certificate,45.clientcert = device_credentials,46};4748struct MvMqttConnectRequest request = {49.protocol_version = MV_MQTTPROTOCOLVERSION_V5,50.host = {51.data = broker_host,52.length = broker_host_len53},54.port = broker_port,55.clientid = {56.data = client,57.length = client_len58},59.authentication = authentication,60.tls_credentials = &tls_credentials,61.keepalive = 60,62.clean_start = 0,63.will = NULL,64};
Issue the request with:
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:
1struct MvMqttConnectResponse {2enum MvMqttRequestState request_state,3uint32_t reason_code4}
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.
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.
Copy the following Statement
object and paste it on over the existing one:
1"Statement": [2{3Effect": "Allow",4"Action": "iot:Connect",5"Resource": "arn:aws:iot:<region>:<account_id>:client/${iot:Connection.Thing.ThingName}"6},7{8"Effect": "Allow",9"Action": "iot:Publish",10"Resource": "arn:aws:iot:<region>:<account_id>:topic/sensor/device/${iot:Connection.Thing.ThingName}"11},12{13"Effect": "Allow",14"Action": "iot:Subscribe",15"Resource": [16"arn:aws:iot:<region>:<account_id>:topicfilter/command/device/${iot:Connection.Thing.ThingName}",17"arn:aws:iot:<region>:<account_id>:topicfilter/command/device/all"18]19},20{21"Effect": "Allow",22"Action": "iot:Receive",23"Resource": [24"arn:aws:iot:<region>:<account_id>:topic/command/device/${iot:Connection.Thing.ThingName}",25"arn:aws:iot:<region>:<account_id>:topic/command/device/all"26]27}28]
<region>
with your own AWS region, and all instances of
<account_id>
with your own AWS account ID (numbers only; omit any hyphens).
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.
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 and click the Contact Support button in the left-hand navbar.