Project

How to Communicate with a Custom BLE using an Android App

September 18, 2015 by Travis Fagerness

Learn how to communicate with a custom BLE peripheral using an Android device.

This article demonstrates how to communicate with a custom BLE peripheral using an Android device. This application can be adapted to add BLE capabilities to other devices by connecting a nRF capable device to them.

Overview

This is part of a series of articles on the nRF51. The nRF51 is a system-on-chip with a Cortex M0 and a BLE radio chip all in one. This article demonstrates how to communicate to a custom peripheral from Android.

Previous articles:
BLE using nRF51: ARM-GCC Build Environment

How to Communicate with a Custom BLE using an Android App

Requirements

  • Device that has the nRF51
    • Used in article: nRF-Dongle
    • Programmed with the application from the article on custom BLE peripheral
  • Android device capable of BLE
  • Java JDK 7
    • Used in article: 7u79
  • Android Studio
    • Use in article: 1.3.2

Installation

Java JDK 7

Download the JDK for your operating system and follow the on-screen instructions to install.

Android Studio

Download the installer for your operating system and follow the on-screen instructions to install.

Create a new project

Open Android Studio and you should be greeted with the following screen.

Select Import an Android code sample. On the next screen select the sample Bluetooth Le Gatt under Connectivity. This project will set us up with a framework to build off of for our application. The sample application has the ability to scan for devices, connect, and display information about services and characteristics.

Name the project what you'd like. I kept it the same name as the default.

Click Finish. The project will now load.

Working with a project

The IDE may seem overwhelming at first, it has many features. 

Let's go through a few of the key features that we need to create a custom application based on this example.

File Browser

The file browser has many categories and displays each file in the projects. The manifests are xml files that setup the project and tell the OS what behaviors and features the application will require. The java folder contains the actual code for the application. The res folder contains the resources, such as the graphical layouts, menus, and values used throughout the application. Values can be strings, arrays, colors and much more. They are used throughout the application code instead of hard coding values.

Layout View

The layout view is automatically opened if the layout file is opened. The layout can be modifed directly by changing to the text view by clicking on the bottom in the bottom left. Remember this because it will be much easier to copy the code in the next section to create a layout.

Java View

If you open one of the java files, a standard text editor will open.

Creating a layout with buttons

We're going to add two buttons to the application that will allow us to read and write the custom characteristics that were created. To do this, we have to create a new layout file by right clicking the layout folder > New > Layout resource. A new window will open to setup some options, leave them as the defaults. Set the name to "button_control". You should now see a new file called "button_control.xml" under the layout folder.

Click the "Text" button on the bottom left to view the XML file itself. Paste the following code into the file button_control.xml.

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
 android:orientation="vertical" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent"> 

 <Button 
  android:layout_width="match_parent" 
  android:layout_height="wrap_content" 
  android:text="Write Char" 
  android:id="@+id/button2" 
  android:onClick="onClickWrite" /> 

 <Button 
  android:layout_width="match_parent" 
  android:layout_height="wrap_content" 
  android:text="Read Char" 
  android:id="@+id/button" 
  android:onClick="onClickRead" /> 

 <TextView 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content" 
  android:text="@string/no_data" 
  android:id="@+id/data_value" /> 
</LinearLayout>

You should see two buttons on the example phone and some text that says "No data". This text field will be used to display the characteristic data.

Add read and write function to BluetoothLeService

In order for the buttons to do anything, we have to modify the BluetoothLeService file to be able to read and write the custom characteristics. Add the following two functions after the function titled getSupportedServices() around line 314. These functions have hard-coded UUID's that were created in the BLE peripheral project.

    public void readCustomCharacteristic() {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        /*check if the service is available on the device*/
        BluetoothGattService mCustomService = mBluetoothGatt.getService(UUID.fromString("00001110-0000-1000-8000-00805f9b34fb"));
        if(mCustomService == null){
            Log.w(TAG, "Custom BLE Service not found");
            return;
        }
        /*get the read characteristic from the service*/
        BluetoothGattCharacteristic mReadCharacteristic = mCustomService.getCharacteristic(UUID.fromString("00000002-0000-1000-8000-00805f9b34fb"));
        if(mBluetoothGatt.readCharacteristic(mReadCharacteristic) == false){
            Log.w(TAG, "Failed to read characteristic");
        }
    }

    public void writeCustomCharacteristic(int value) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        /*check if the service is available on the device*/
        BluetoothGattService mCustomService = mBluetoothGatt.getService(UUID.fromString("00001110-0000-1000-8000-00805f9b34fb"));
        if(mCustomService == null){
            Log.w(TAG, "Custom BLE Service not found");
            return;
        }
        /*get the read characteristic from the service*/
        BluetoothGattCharacteristic mWriteCharacteristic = mCustomService.getCharacteristic(UUID.fromString("00000001-0000-1000-8000-00805f9b34fb"));
        mWriteCharacteristic.setValue(value,android.bluetooth.BluetoothGattCharacteristic.FORMAT_UINT8,0);
        if(mBluetoothGatt.writeCharacteristic(mWriteCharacteristic) == false){
            Log.w(TAG, "Failed to write characteristic");
        }
    }

Add read and write function to DeviceControlActivity

The BluetoothLeService is just the underlying class that abtracts some of the BLE Android functionality. In order to actually do anything from the UI, some modifications have to be done to the file DeviceControlActivity. Add the following two functions to the end of the class just after makeGattUpdateIntentFilter(). These functions just call the two functions that were added to the class from before. The onClickWrite() function writes the value 0xAA to the characteristic, but this could be modified to any value you wish. The read function triggers a read request to the operating system. When the read is complete the data is transfered through an intent which is read from mGattUpdateReceiver with the action ACTION_DATA_AVAILABLE. Intents and actions are a feature of Android and are beyond the scope of this article.
.

    public void onClickWrite(View v){
        if(mBluetoothLeService != null) {
            mBluetoothLeService.writeCustomCharacteristic(0xAA);
        }
    }

    public void onClickRead(View v){
        if(mBluetoothLeService != null) {
            mBluetoothLeService.readCustomCharacteristic();
        }
    }

Modification to DeviceControlActivity

The class as written will try to access some UI features that don't exist in the layout we created. To ensure the application doesn't crash, some lines have to be commented out. We also have to start the layout that we created.


Function mGattUpdateReceiver

Comment out line 109: 
//displayGattServices(mBluetoothLeService.getSupportedGattServices());
 

Function clearUI

Comment out line 151: 
//mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);


Function onCreate

Comment out the UI references. Keep the reference to mDataField. Change the setContentView function to load the button_control layout.

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.button_control);

        final Intent intent = getIntent();
        mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME);
        mDeviceAddress = intent.getStringExtra(EXTRAS_DEVICE_ADDRESS);

        // Sets up UI references.
        /*
        ((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress);
        mGattServicesList = (ExpandableListView) findViewById(R.id.gatt_services_list);
        mGattServicesList.setOnChildClickListener(servicesListClickListner);
    mConnectionState = (TextView) findViewById(R.id.connection_state);
    */
        mDataField = (TextView) findViewById(R.id.data_value);

        getActionBar().setTitle(mDeviceName);
        getActionBar().setDisplayHomeAsUpEnabled(true);
        Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
        bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
    }
Function updateConnectionState

Remove line 235:
//mConnectionState.setText(resourceId);
 

Testing the App

First, make sure the peripheral is turned on and advertising. Download the application by connecting your phone to the PC and clicking the green arrow. A window will pop-up to choose the device to run on. If you don't see your device, you may need to install ADB drivers and set your device to USB debuggable. This is different for every device.

The application will open on your phone. It will automatically begin scanning. Select the "Custom BLE" peripheral. Now you can read and write data by pressing the buttons. When you write, you can see the data on the UART console. When you read, you can see data on the phone increasing by a value of 1 every second. The data is displayed as ASCII text and the raw byte values.

Here I pressed the button read char and can see the data values of 0x6A which is 'j' in ASCII.

Conclusion

This article demonstrated how the BLE example in Android Studio can be modified for whatever UUID you have. This application can be adapted to add BLE capabilities to other devices by connecting a nRF capable device to them. This could be through GPIO, SPI, UART, or I2C. Future articles will show some examples of doing this. Have fun!

 

Give this project a try for yourself! Get the BOM.

BluetoothLeGatt.zip

60 Comments
  • Stuart Allsopp January 27, 2016

    Great little tutorial just what I’ve been looking for. I’m still struggling to understand the UUID protocol.

    Like. Reply
    • Leytronix June 14, 2019
      Indeed great tutorial, however the link to the code is invalid. Does anyone have the code?
      Like. Reply
      • K
        Kate Smith June 18, 2019
        Thanks for reading! We're able to download the file on our end—can you tell us if you're logged in? If so, we'll have to investigate further to figure out what the issue is.
        Like. Reply