IOT 101: From hardware to cloud — Part II

Mobile Application — Getting started with Android

Leonardo Cavagnis
9 min readSep 7, 2020

IOT is like teenage sex: everyone talks about it, nobody really knows how to do it.

In my previous article, I’ve talked about how to build an IOT gadget using Arduino.
Now, it’s time to let the gadget communicate with the smart devices world (Smartphone, Tablet, etc.). I chose the BLE (Bluetooth Low Energy) as communication channel.

Read Part I of this mini-series to know more about Bluetooth Low Energy fundamentals.

BLE is the best way to communicate few data in a short range with a Smartphone. For this reason, most common IOT devices (e.g., Smartwatch) use Bluetooth.

In this article, we develop a simple Mobile Application with Android.
The goal of this app is to communicate with the Smart Thermostat performing this two activities:

  • Read the measured temperature
  • Change the status of a light (LED)

…Let’s start!

Apps for everything

Why is so important to learn how to build a mobile app?

We live in the era of app for everything”.
So, experts, hobbyists, … every person that deal with technology and software have to learn some mobile application development concepts.

Android or iOS? Green robot is your friend

Android is an operating system, owned by Google, used in smartphones and tablets. iOS, its rival, is owned by Apple and it is used in i-Devices (iPhone, iPad, etc.).

Let’s not waste time saying which system is better than the other, they are both excellent systems with pros and cons.
However, let me explain why I chose Android:

  • Android is the most used Mobile OS.
  • Android devices are cheapest and easiest to find. (…I bet you have an old Android phone still working in your drawer!)
  • Android Studio IDE (software to create the Android apps) works on every computers and OS.

Starting with these assumptions, I took an old Android device and I’ve started playing with it.

Android mobile application can be developed using two programming languages: Java or Kotlin. In the official Kotlin site you can find the comparison between the two programming languages.

Personally, I chose Kotlin because is the official programming language suggested by Google.

Let’s befriend with Android

First of all, download Android Studio: the official IDE to create Android app.

To getting started with Android, I recommend to start from “Basic Activity” project template.

It’s a good starting point to getting familiar with a lot of fundamentals concept of Android development.
On the web there are several video tutorials on how to develop Android app in Kotlin. I followed this Pluralsight course, but you can also find a lot of free similar stuff on Youtube, too.

…Let’s have fun!

Smart Thermostat App

Smart Thermostat app is divided in two sections:

  1. Scan
    It shows the result of BLE Scanner. All nearby Smart Thermostat devices are listed with their local name and MAC Address. User has to select a device from the list to initiate a Bluetooth connection session.
  2. Connect
    This section is shown only if the smartphone is correctly connected to a Smart Thermostat. It allows user to interact with the device (Read temperature and change LED status).

Android, why is so difficult to make BLE work?

Making BLE works on Android is not an easy “game”.

This because:

  • Android documentation for BLE is very basic. There are few examples; I surfed the web for days before finding something useful…
  • Android BLE APIs manages the Bluetooth antenna at very low-level. So, you need to study BLE concepts before starting to develop.
  • There are a lot of third-party Android BLE libraries, but none is considered the best.
  • BLE is not very stable in Android devices. Android OS is installed in a lot of devices with different Bluetooth antenna. This means that in some devices BLE doesn’t work very well…

But, don’t panic! I’ve made a lot of work for you and I’ve developed a fully workable BLE Mobile app in Android.

Now, it’s time to code!

Permissions: hey robot, can I use the BLE antenna?

In order to use Bluetooth on Android devices, you must first ask Android system the “permission” to use it.

Just add these few lines in the app manifest file (AndroidManifest.xml) of your Android Studio project:

<uses-permission android:name="android.permission.BLUETOOTH"/><uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/><uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
  • android.permission.BLUETOOTH
    This permission is needed for Bluetooth communication in general.
  • android.permission.BLUETOOTH_ADMIN
    This one is needed for managing Bluetooth settings.
  • android.permission.ACCESS_FINE_LOCATION
    This one is needed because Bluetooth scan can be used to gather information about the location of the user (e.g., BLE Beacon).

I addressed this topic in my article on Contact Tracing using Bluetooth.

  • android.hardware.bluetooth_le
    The last one is not a “real” permission, but a constraint. It is used to ensure that the app is available only for Bluetooth Low Energy capable devices.

Since ACCESS_FINE_LOCATION is considered “dangerous” by Google, it’s necessary to ask the permission also to the user.

These lines of code show the location permission user request popup:

// Check ACCESS_FINE_LOCATION permission
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted, request it to the user
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_FINE_LOCATION)) {
// Display UI and wait for user interaction
} else {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
PERMISSION_REQUEST_FINE_LOCATION)
}
} else {
// Permission has already been granted
}

Scan: what’s around me?

Before connecting to a BLE device you must first scan for it.
The Android function to start a scan of BLE devices is:

bleScanner?.startScan(bleScanFilter, bleScanSettings, mScanCallback)

The startScan function requires three important parameters:

  • Filters (bleScanFilter)
    The most common approach is filtering by devices that expose specific Service UUIDs.
var bleScanFilter: MutableList<ScanFilter?>? = null
bleScanFilter = ArrayList()
for (serviceUUID in bleScanServiceUUIDs) {
val filter = ScanFilter.Builder()
.setServiceUuid(ParcelUuid(serviceUUID))
.build()
bleScanFilter.add(filter)
}

In Smart Thermostat app, we filter for Environmental Sensing Service (ESS):

val ENVIRONMENTAL_SENSING_SERVICE_UUID: UUID = UUID.fromString("0000181A-0000-1000-8000-00805f9b34fb")val bleScanServiceUUIDs: Array<UUID> = arrayOf(ENVIRONMENTAL_SENSING_SERVICE_UUID)
  • Settings (bleScanSettings)
    There are a lot of settings, below the most used:
    - SCAN_MODE_LOW_POWER: Scan is slow (scan for 0.5 sec every 4.5 sec) but it is low power consumption.
    - CALLBACK_TYPE_ALL_MATCHES: Callback is prompted every time a device matches the filter.
    - MATCH_MODE_AGGRESSIVE: All devices that match the filters are caught, even ones with feeble signal strength.
    - MATCH_NUM_ONE_ADVERTISEMENT: You only need one advertising packet to match a device.
val bleScanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
.setReportDelay(0)
.build()
  • Callback (mScanCallback)
    Callback function is used to analyze the results of BLE scan.

To summarize, the BLE scanner starts according to Settings, looking for devices that match Filters and, when it finds one, it will call the Callback function.

Below the complete function to scan BLE devices:

private fun scanLeDevice() {
val bleScanner = bleAdapter?.bluetoothLeScanner

// Scan settings
val bleScanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
.setReportDelay(0)
.build()

// Scan filters
var bleScanFilter: MutableList<ScanFilter?>? = null
if (bleScanServiceUUIDs != null) {
bleScanFilter = ArrayList()
for (serviceUUID in bleScanServiceUUIDs) {
val filter = ScanFilter.Builder()
.setServiceUuid(ParcelUuid(serviceUUID))
.build()
bleScanFilter.add(filter)
}
}
bleScanner?.startScan(bleScanFilter, bleScanSettings,
}

Connection and disconnection

After finding the device through scan, you can connect to it.
To do this, use function:

bleDevice.connectGatt(context, false, bluetoothGattCallback, TRANSPORT_LE)

Connection is performed using the physical bluetooth address (MAC Address) of the device:

bleDevice = bleAdapter!!.getRemoteDevice(bleDeviceAddress)

For disconnection, you don’t need the device address, simply use:

bleGatt.disconnect()

Services and Characteristics

As mentioned in the previous article, Services and Characteristics are the way in which a peripheral device (e.g., Smart Thermostat) exposes a set of data to a central device (Smartphone).

Services discovery: “Tell me what you do and I’ll tell you who you are”

Before interacting with Services and Characteristics, Smartphone asks to the Smart Thermostat: “Hey, what type of data do you have and how are they organized?”.

This procedure is called: Services Discovery

Services discovery is usually done immediately after connection.
After calling the connection function (connectGatt()), system lets you know the result of connection on the callback functiononConnectionStateChange(). If connection is successful, you call the functiondiscoverServices()to start the discovery procedure.

fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if(status == BluetoothGatt.GATT_SUCCESS) {
when (newState) {
BluetoothProfile.STATE_CONNECTED -> {
gatt.discoverServices()
}
BluetoothProfile.STATE_DISCONNECTED -> {
gatt.close()
}
else -> {
//Unknown state
}
}
}
}

Characteristics: Let’s play with data!

Read

In this project, Smartphone requests to Smart Thermostat the measured temperature reading the Temperature Characteristic.

To read a data from a specific characteristic, use function:

bleGatt.readCharacteristic(characteristic)

readCharacteristic() function lets you know the result in the callback function onCharacteristicRead():

fun onCharacteristicRead(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
if (characteristic != null) {
//Read Characteristic data content
}
}
else -> {

}
}
}

Below, how Smartphone request temperature from Smart Thermostat:

val ESS_TEMPERATURE_CHARACTERISTIC_UUID: UUID = UUID.fromString("00002A6E-0000-1000-8000-00805f9b34fb")for (service in bleDiscoveredServices) {
if (
service.uuid.compareTo(ENVIRONMENTAL_SENSING_SERVICE_UUID) == 0) {

for (characteristic in service.characteristics) {
if (characteristic.uuid.compareTo(ESS_TEMPERATURE_CHARACTERISTIC_UUID) == 0) {
readCharacteristic(characteristic)
}
}
}
}

Write

The Smartphone can change the status of a light mounted on Smart Thermostat, writing data on the Led Characteristic.

To write a data in a specific characteristic, use function:

characteristic.value     = writeData
characteristic.writeType = PROPERTY_WRITE
bleGatt.writeCharacteristic(characteristic)

where writeData is an array containing the data you want to write.

Below, how to turn on LED in Smart Thermostat:

val LED_SERVICE_UUID: UUID 
= UUID.fromString("277eaf48-6698-4da9-8329-335d05343490")
val LS_LED_STATUS_CHARACTERISTIC_UUID: UUID
= UUID.fromString("277eaf49-6698-4da9-8329-335d05343490")
for (service in bleDiscoveredServices) {
if (service.uuid.compareTo(LED_SERVICE_UUID) == 0) {
for (characteristic in service.characteristics) {
if (characteristic.uuid.compareTo(LS_LED_STATUS_CHARACTERISTIC_UUID) == 0) {
var ledStatus:Byte = 1 //ON

writeCharacteristic(characteristic, WRITE_TYPE_DEFAULT, byteArrayOf(ledStatus))
}
}
}
}

Notify

Smartphone can listen environment temperature changing, enabling Notify feature on Temperature Characteristic.

To monitor the data changes (i.e. Notify) of a specific characteristic, use function:

var descriptor = characteristic.getDescriptor(UUID.fromString(CCC_DESCRIPTOR_UUID))bleGatt.setCharacteristicNotification(descriptor.characteristic, true)

Below, how to enable the notify feature on Temperature Characteristic:

for (service in bleDiscoveredServices) {
if (service.uuid.compareTo(ENVIRONMENTAL_SENSING_SERVICE_UUID) == 0) {
for (characteristic in service.characteristics) {
if (characteristic.uuid.compareTo(ESS_TEMPERATURE_CHARACTERISTIC_UUID) == 0) {
Log.i(TAG, "Temperature characteristic found")

val enableNotification: Boolean = true

setNotify(characteristic, enableNotification)
}
}
}
}

When temperature changes, the result is communicated in the callback function onCharacteristicChanged():

fun onCharacteristicChanged(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic?) {

if (characteristic != null) {
when (characteristic.uuid) {
ESS_TEMPERATURE_CHARACTERISTIC_UUID -> {
essTemperature = (characteristic.getIntValue(FORMAT_UINT16, 0) / 100).toFloat();
}
else -> {
Log.e(TAG, "Error")
}
}
}

…That’s all, folks!

What’s next? Talking to a Cloud system

Now that we have covered the topic on how to build an Android app with BLE, we will go in-depth on how to send data to a Cloud system and how to show them on a web dashboard.

Here, you can find the Android Mobile Application of Smart Thermostat project! It covers all the topics discussed in this article!

Thanks to my coworker and friend Alessandro Persiano who helped me on dealing with the Android world.

References

Read the series of Medium articles Making Android BLE work to learn more about topics addressed in this article.

--

--

Leonardo Cavagnis

Passionate Embedded Software Engineer, IOT Enthusiast and Open source addicted. Proudly FW Dev @ Arduino