Enterprise Android in a Hosted Cloud IoT Solution: Part 4:Connecting a Zebra device to Amazon Web Services (AWS)
This is the fourth part in a series of blog posts looking at integrating Zebra Android devices as IoT clients in a public cloud infrastructure. For other posts in this series, please see the links below:
- Part 0 – Enterprise Android in a Hosted Cloud IoT Solution: Introduction
- Part 1 – Google Cloud IoT Core Data Processing
- Part 2 – Interfacing a Zebra mobile computer with Google Cloud IoT
- Part 3 – Amazon Web Services (AWS) IoT Core Data Processing
- Part 4 – Interfacing a Zebra mobile computer with AWS IoT
- Part 5 – Microsoft Azure IoT Hub Data Processing
- Part 6 – Interfacing a Zebra mobile computer with Azure IoT Hub
This post expands on the concepts already discussed in Part 3 – AWS IoT Core Data Processing
In part 3 of this post series, we covered simulating an MQTT message using the MQTT client handily provided by AWS and in this post we will replace that test client with an MQTT client on a real device. To achieve this we will lean heavily on an official AWS Android PubSub example which uses the AWS SDK for IoT to connect an Android device to AWS and generates its own certificates to do so. Full credit to the original authors.
Setting up permissions to creating the client identity and keys
To connect the client (or ‘thing’ in AWS parlance) it is necessary to generate a certificate and key, the private key remaining on the device and the certificate being placed on the server to ensure secure connection with the client. I will duplicate the steps given by the official official AWS Android PubSub example but alternative ways do exist, e.g. generating the key and certificate in the cloud.
- In the Amazon Cognito Console, press the Manage Identity Pools button and on the resulting page press the Create new identity pool button
- Give your identity pool a name and ensure that ‘Enable access to unauthenticated identities’ under the Unauthenticated identities section is checked. This allows the sample application to assume the unauthenticated role associated with this identity pool. Press the Create Pool button to create your identity pool. This tutorial uses ‘ent_android_iot’ for the identity pool name.
- As part of creating the identity pool, Cognito will setup two roles in Identity and Access Management (IAM). These will be named something similar to: Cognito_<<PoolName>>Auth_Role and Cognito_<<PoolName>>UnauthRole. You can view them by pressing the View Details button on the console. Now press the Allow button to create the roles. This tutorial uses ‘Cognito_ent_android_iotAuth_Role’ and ‘Cognito_ent_android_iotUnauth_Role’.
- Note the Identity pool ID value. It should look similar to: ‘us-west-2: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx". Also, note the region that is being used. These will be used in the application code later.
- Next, we attach a policy to the unauthenticated role to setup permissions to access the required AWS IoT APIs. This is done by first creating the IAM Policy shown below in the IAM Console and then attaching it to the unauthenticated role. In the IAM console, search for the pool name that you created and click on the link for the unauth role.
- Click on the “Add inline policy” button and add the following policy using the JSON tab. Click on “Review Policy”, give the policy a descriptive name and then click on “Create Policy”. This policy allows the sample app to create a new certificate including a private key and attach a policy to the certificate.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:AttachPrincipalPolicy",
"iot:CreateKeysAndCertificate"
],
"Resource": [
"*"
]
}
]
}
Note from the tutorial on which this post is based:
To keep this example simple it makes use of unauthenticated users in the identity pool. This can be used for getting started and prototypes but unauthenticated users should typically only be given read-only permissions if used in production applications. More information on Cognito identity pools can be found here, information on AWS IAM roles and policies can be found here, and information on AWS IoT policies can be found here.
Creating the Policy
The configuration we have setup up to this point will enable the Sample App to connect to the AWS IoT platform using Cognito and upload certificates and policies. Next, we will need to create a policy that we will attach to the Device Certificate that will authorize the certificate to connect to the AWS IoT message broker and perform publish, subscribe and receive operations. To create the policy in AWS IoT
- Navigate to the AWS IoT Console and press the Get Started button. On the resulting page click on Secure on the side panel and then click on Policies
- Click on Create
- Give the policy a name (this tutorial uses ‘ent-andorid-policy’). Note this name as you will use it in the application.
- Click on Advanced Mode and replace the default policy with the following text and then click the Create button.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iot:Connect",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"iot:Publish",
"iot:Subscribe",
"iot:Receive"
],
"Resource": "*"
}
]
}
Note from the tutorial on which this post is based:
To keep things simple, This policy allows access to all the topics under your AWS IoT account. This can be used for getting started and prototypes. In product, you should scope this policy down to specific topics. Specify them explicitly as ARNs in the resource section: "Resource": "arn:aws:iot:<REGION>:<ACCOUNT ID>:topic/<<mytopic/mysubtopic>>".
Creating the client identity and keys
I have put together a sample client which can be used to connect to supported cloud IoT solutions and send both dummy and real data. Please clone the client app from GitHub and run it on a device, being sure to grant any requested runtime permissions.
The sample app depends on the AWS IoT client API which is available from gradle:
implementation 'com.amazonaws:aws-android-sdk-iot:2.8.+'
Firstly, configure the sample app with the connection information required for AWS
Setting | Description |
---|---|
Endpoint |
The custom endpoint allows you to connect to AWS IoT and will be in the form xxxxxxxxxxxxxxx-ats.iot.region.amazonaws.com. You can find this by loading the AWS IoT dashboard then clicking Settings. |
Cognito Pool | This is the identity pool ID you noted down earlier, in the form us-west-2: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. You can retrieve it from the Cognito console and clicking Manage Identity Pools |
Policy Name | This is the policy name you created previously, this tutorial uses ‘ent-android-policy’ |
Cloud Region | This tutorial uses us-west-2. Note that it should be one of these regions. |
You can either modify the connection settings using the UI of the sample app or (far more likely) modify the UserConfig.java file.
To create the client identity and keys:
- Launch the application
- Select the AWS radio button
- Click ‘Connect’
- You should see a message that says “Test client connect failed: Certificates have not been created, please try again”. This is a good thing.
- Click ‘Connect’ again
- Now the client should successfully connect
If at any point you want to regenerate the private key and certificate then you will need to uninstall and reinstall the client application. In a production app you would want to implement a more seamless way of rotating keys(!)
The code to connect the client to the AWS IoT endpoint is as follows:
mqttManager.connect(clientKeyStore, new AWSIotMqttClientStatusCallback() {
@Override
public void onStatusChanged(final AWSIotMqttClientStatus status,
final Throwable throwable) {
if (status == AWSIotMqttClientStatus.Connecting) {
Log.d(TAG, "Connecting...");
} else if (status == AWSIotMqttClientStatus.Connected) {
connected = true;
Log.d(TAG, "Connected");
} else {
// Handle other statuses
}
}
});
Observe that if you now load up the AWS IoT dashboard and click on Secure, then Certificates, a certificate will have been created.
Similarly, notice how a policy has also been created under the Policies tab
Sending device data from the client to AWS IoT
At this point, everything is configured to send either dummy or real device data from the client and process it within AWS IoT. The following steps detail how to use the sample client available from GitHub to send both dummy and real data from the device.
Sending dummy data
After generating the AWS client certificate and key and successfully connecting to AWS, tap the ‘Send Data’ tab. From this table enable the “Send Dummy Data” switch and you should see something like the below
Scroll down to the bottom of the view and click the “Send Dummy Data” button, you should see an indication that the data was successfully sent
This is publishing a message to the MQTT topic defined in UserConfig.java, this tutorial uses ‘deviceTelemetry’.
final String topic = UserConfig.AWS_TOPIC;
long dt = System.currentTimeMillis();
DateFormat formatter = new SimpleDateFormat("YYYY-MM-dd'T'HH:mm:ss");
String dateTime = formatter.format(dt);
final String message = "{\n" +
"\"deviceId\": \"" + deviceId + "\",\n" +
"\"dateTime\": \"" + dateTime + "\",\n" +
"\"model\": \"" + model + "\",\n" +
"\"lat\": \"" + lat + "\",\n" +
"\"lng\": \"" + lng + "\",\n" +
"\"battLevel\": \"" + battLevel + "\",\n" +
"\"battHealth\": \"" + battHealth + "\",\n" +
"\"osVersion\": \"" + osVersion + "\",\n" +
"\"patchLevel\": \"" + patchLevel + "\",\n" +
"\"releaseVersion\": \"" + releaseVersion + "\"\n" +
"}\n";
mqttManager.publishString(message, topic, AWSIotMqttQos.QOS0);
Sending real data
To send real data, first select the ‘AWS’ radio button from the ‘Connect’ tab and disconnect from the test connection. In the Send data pane, slide the ‘Send Real Data’ toggle to on.
A WorkManager has now been started which will send device data in the background about every 15 minutes, though this frequency will be impacted by the device going into doze so you may not see messages this frequently.
The code to create the periodic request is given below and every roughly 15 minutes the worker is will connect to the cloud IoT service, publish the real device data and then disconnect from the cloud IoT service. Because the worker needs to connect, we pass the required connection information.
PeriodicWorkRequest.Builder sendRealDataBuilder =
new PeriodicWorkRequest.Builder(SendRealDataWorker.class,
PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, TimeUnit.MILLISECONDS);
// Pass the MQTT connection information to the worker. The long-term worker is responsible for
// making and breaking the MQTT connection when needed.
Data metaData = new Data.Builder().putInt(MQTT_SERVER_ENDPOINT, radioGroup.getCheckedRadioButtonId())
.putString(MQTT_DEVICE_ID, txtDeviceId.getText().toString()) // Common
.putString(MQTT_CLOUD_REGION, txtCloudRegion.getText().toString()) // Common
.putString(MQTT_AWS_ENDPOINT, txtEndpoint.getText().toString()) // AWS
.putString(MQTT_COGNITO_POOL_ID, txtCognitoPoolId.getText().toString()) // AWS
.putString(MQTT_POLICY_NAME, txtPolicyName.getText().toString()) // AWS
.build();
PeriodicWorkRequest sendRealDataWork = sendRealDataBuilder.
setInputData(metaData).build();
WorkManager.getInstance().enqueue(sendRealDataWork);
sendRealDataWorkId = sendRealDataWork.getId();
The actual work that gets performed every roughtly 15 minutes is defined in the doWork() function of the SendRealDataWorker class.
The following data is sent periodically:
- Device build information (model, os version, patch level, release version)
- Device location (lat, long)
- Device battery status (level, health)
Note that battery health is only available on Zebra Android devices, http://techdocs.zebra.com/emdk-for-android/7-0/guide/reference/refbatteryintent/
Creating a link to an AWS IoT thing
Having shown how to send a store real data from a Zebra Android device into the AWS IoT Hub, this tutorial is complete however AWS offers a “Device Shadow Service” for IoT devices, allowing you to define the device state in a JSON document.
For completeness, the following section will explain how to create an AWS ‘Thing’ which represents our Android device
- In the AWS IoT dashboard click ‘Manage’, ‘Things’ and then click the ‘Create’ button
- Click to ‘Create a single thing’
- Assign a name to the thing, this tutorial uses ‘test-device’ and do not worry about applying a type or group to the thing at this stage
- The next step is to assign certificates. We will be assigning the certificate which has already been generated by our device so select ‘Create thing without certificate’
- Your ‘thing’ should now appear under the list of ‘Things’
- Again, in the AWS IoT dashboard click ‘Secure’ then ‘Certificates’. Locate the certificate which was generated by the physical device, click the three dots and select ‘Attach thing’
- Attach the thing you just created, in the case of this tutorial, ‘test-device’
- Back in the AWS IoT dashboard, if you click on ‘Manage’, ‘Things’ then click on the thing you just created, (e.g. ‘test-device’). Under ‘Security’ you should see the certificate you just attached
- At this stage your physical device is now officially an AWS ‘Thing’ and you should be able to interact with it via the endpoints defined under the ‘Interact’ tab, though that is out of scope for this tutorial.
Visualising the data
Having stored the device data in a cloud storage database (DynamoDB), the next logical step would be to provide a way of visualising the data to see where devices are located, what the status of those devices are and whether any of the devices need new batteries or an OS update.
DynamoDB does not offer a simple REST API to access data. The best way is to create a lambda function to access the data and expose a REST API via the Amazon API gateway, as shown below:
The steps are:
- Create a lambda to do the database query and data retrieval using the AWS DynamoDB DocumentClient
- Define an API gateway to expose a REST API
- Convert the JSON response from the REST API into whichever format best suits the IoT deployment, for example a browser-based dashboard
Although this post will not specifically walk through the steps to visualise the data, you can see this in principle in the original tutorial on which this post is based; although that tutorial is concerned with writing data to the database rather than retrieving data from the database.
Darryn Campbell