Enterprise Android in a Hosted Cloud IoT Solution: Part 2: Connecting a Zebra device to Google Cloud Platform (GCP)
This is part two 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 1 – Google Cloud IoT Core Data Processing
In part 1 of this post series we covered simulating an MQTT client in java (code here); that code uses the org.eclipse.paho.client.mqttv3 library so in this post we will wrap that same connection / publishing code into an Android application to send both simulated and real data to Google’s cloud platform.
Connection information
In part 1 of this post, we created a test device in the IoT core registry and assigned an RS256_X509 public key to that device, using the corresponding private key in the simulator. To keep things simple we will reuse that same test device which will now represent our physical Zebra Android device.
We need the following connection information:
Setting | Description |
---|---|
Project ID |
This is the GCP project ID which you can find in the Project info pane in your GCP dashboard. The previous post used the value “ent-android-iot-server-gcp” which this post will also use |
Registry ID |
This is the GCP registry ID associated with the IoT Core registry being used by your project, this will be shown when you select the IoT Core product. The previous post used the value “ent-android-iot-registry-gcp” which this post will also use |
Private Key |
This is the private RSA key unique to the device instance, it should match the public equivalent assigned to the device representation in GCP. The previous post uses “rsa_private_pkcs8” and you need to copy this file to the device’s external storage.
Key management and distribution is its own complex topic. To keep things simple, the sample app will read the file from the external storage directory but in a production environment this would be very bad practice. |
Algorithm |
The algorithm used to generate the keys, either RS256 or ES256. The previous post used the value “RS256” which this post will also use |
Cloud Region |
The cloud region where the registry is located. You can find this in the same place as the registry ID and will be shown when you select the IoT Core product. The previous post used the value “europe-west1” which this post will also use |
Device ID |
This is the device id assigned to the device when you created it and will be listed under the Registry details. The previous post used the value “test-device” which this post will also use |
The sample app
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 Zebra mobile computer, being sure to grant any requested runtime permissions.
To use the eclipse MQTT client, ensure to download the jar file and include it in your gradle dependencies:
implementation files('libs/org.eclipse.paho.client.mqttv3-1.1.1.jar')
Future parts of this post series will integrate with AWS and Azure. Integrating all 3 client libraries causes some conflicts since the AWS SDK contains its own paho client but to just connect with GCP, the above gradle dependency will suffice. The client app in GitHub will work with all 3 providers without rebuilding.
Copy security key
As mentioned in the previous table key management and distribution is its own complex topic but to keep things simple the sample app will read the private key from external storage (please do NOT do this in production).
The external storage location might change depending on which device you are using but in the case of my test device the command to push the key file is as follows:
adb push rsa_private_pkcs8 /storage/emulated/0/rsa_private_pkcs8
The test app will generate an error if it cannot find the key at the expected location.
Testing the connection
If you have not already done so, clone the client app from GitHub and run it on a device, you should see something similar to the below:
Either modify the connection settings to match your own or modify the UserConfig.java file to change these defaults (strongly recommended since the values are not persisted otherwise)
Use the ‘Connect’, ‘Disconnect’ and ‘Test’ buttons. The status message at the top of the UI should update to show whether the connection succeeded.
Test connection to GCP has succeeded |
Test connection to GCP has been successfully disconnected |
final String mqttServerAddress =
String.format("ssl://%s:%s", mqttBridgeHostname, mqttBridgePort);
final String mqttClientId =
String.format(
"projects/%s/locations/%s/registries/%s/devices/%s",
projectId, cloudRegion, registryId, deviceId);
MqttConnectOptions connectOptions = new MqttConnectOptions();
connectOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
Properties sslProps = new Properties();
sslProps.setProperty("com.ibm.ssl.protocol", "TLSv1.2");
connectOptions.setSSLProperties(sslProps);
connectOptions.setUserName("unused");
DateTime iat = new DateTime();
if (algorithm.equals("RS256")) {
connectOptions.setPassword(
createJwtRsa(projectId, privateKeyFile).toCharArray());
} createJwtEs(projectId, privateKeyFile).toCharArray());
else {
lastConnectionError = "Invalid algorithm " + algorithm + ". Should be one of 'RS256' or 'ES256'.";
return false;
}
client = new MqttClient(mqttServerAddress, mqttClientId, new MemoryPersistence());
Sending dummy data
After connecting to GCP and observing a successful connection status message, tap the ‘Send Data’ tab. From this tab 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 at the top of the UI.
Test message sent to GCP at 12:04:48
This is publishing a message to the MQTT topic created in the previous post ("device_telemetry") which is associated with the registry:
long dt = System.currentTimeMillis();
String payload =
String.format(
"%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", deviceId, Long.toString(dt), model, lat,
lng, Integer.toString(battLevel), Integer.toString(battHealth),osVersion, patchLevel,
releaseVersion);
String topic = String.format("/devices/%s/events", deviceId);
MqttMessage message = new MqttMessage(payload.getBytes());
message.setQos(1);
client.publish(topic, message);
Sending real data
To send real data, first select the ‘GCP’ radio button from the ‘Connect’ tab but make sure you are disconnected from any 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. Due to the nature of Android background applications a device which enters doze mode might not send its data for several hours.
The code to create the periodic request is given below, every roughly 15 minutes the worker 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_PROJECT_ID, txtProjectId.getText().toString()) // GCP
.putString(MQTT_REGISTRY_ID, txtRegistryId.getText().toString()) // GCP
.putString(MQTT_PRIVATE_KEY_NAME, txtPrivateKeyName.getText().toString()) // GCP
.putString(MQTT_ALGORITHM, txtAlgorithm.getText().toString()) // GCP
.putString(MQTT_CLOUD_REGION, txtCloudRegion.getText().toString()) // Common
.build();
PeriodicWorkRequest sendRealDataWork = sendRealDataBuilder.
setInputData(metaData).build();
WorkManager.getInstance().enqueue(sendRealDataWork);
sendRealDataWorkId = sendRealDataWork.getId();
The actual work that gets performed roughly every 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/latest/guide/reference/refbatteryintent/
If you followed the cloud configuration as described in part 1, you will now see real device data being written to BigQuery (notice the increasing battery level indicating the device was being charged over this time):
Visualising the data
Having stored the device data in a cloud storage database (BigQuery), 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.
To achieve this with BigQuery you can make use of the BigQuery REST API. To use the REST API you will first need to obtain an authorization token, as explained in Google’s documentation for authorizing API requests.
Once authorized, you can call the REST API to retrieve data stored in BigQuery and present that data to your users / administrators however you see fit. If you have followed this tutorial you could retrieve the data as follows:
HTTP GET https://www.googleapis.com/bigquery/v2/projects/{Project ID}/datasets/{Dataset}/tables/{Table}/data
Or, specifically for the values used in this tutorial series:
HTTP GET https://www.googleapis.com/bigquery/v2/projects/ent-android-iot-server-gcp/datasets/iotds/tables/device_data/data
Calling this with Postman would give something like the below:
It is left as an exercise to the reader to convert this JSON response into whichever format best suits their IoT deployment, for example a browser-based dashboard.
Anonymous (not verified)