UART GATT Server (Peripheral) on Android

In most cases Android is used as client (GATT Client). But one can also use Android as GATT Server. There are use cases where running a GATT Server on Android can be useful. For example lets say you want a desktop app displays notification of a SMS arrival. Its easy to write a GATT server (on Phone) that pushes the message to Client (Desktop) as and when it arrives. Now, there can be many other useful use-cases but in the example code I am implementing the UART Protocol over BLE.

There are UART client apps and libraries for mobiles, desktops. But UART GATT Servers are usually written for sensors, tags etc. I think its useful to have a UART GATT Server for Android too. That way we can have Android to Android or Android to Desktop communication over simple protocol. What application you will write over this protocol is left to your imagination.

The UART over BLE Protocol flow.

The UART over BLE Protocol flow.

In the example code below I am writing a simple server, once connecting it responds to commands like whoami or date etc. If it can’t figure then it just echos whatever client has sent. The diagram above should explain the flow. Lets jump into code part.

The SDK version should be at least 21 and we need Bluetooth admin permissions. So enable them in AndroidManifest.xml

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="21" />

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

Define the UUIDs for the service and characteristics. I have written about them previously. You can get more info there. All the permissions are from client perspective.

Important to note that we need Client Characteristic Configuration 0x2902 defined on GATT Server so clients can enable and receive notification. We have defined them in a class called UARTProfile.java

    //Part of UARTProfile.java
    //Service UUID to expose our UART characteristics
    public static UUID UART_SERVICE = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
    //RX, Write characteristic
    public static UUID RX_WRITE_CHAR = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");
    //TX Read Notify
    public static UUID TX_READ_CHAR = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e");
    public static UUID TX_READ_CHAR_DESC = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
    public final static int DESCRIPTOR_PERMISSION = BluetoothGattDescriptor.PERMISSION_WRITE;

GATT Servers work similar to any other service providing API servers. You define services with permissions and expose them. Clients connect to server, explore the services. Write to a characteristic, read from a characteristic or get notified. In our example all of it happens in a MainActivity. I have bits of code here to explain the process.

First we need to get an instance of mBluetoothManager so we create a GattServer. mGattServerCallback is an instance of BluetoothGattServerCallback which handles the request from clients to this server. We will look into it later. After that we will initialize the server with services and start advertising so clients can find.

//....
mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
//...
mGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback);
//...
initServer();
startAdvertising();
//...

We need to define the services and characteristics that we are going to provide as part of our GATTServer. This is done a part of initServer method in our case. Start with defining services and Characteristics. Make sure property types and permissions are right and the add them to the GattServer.

    private void initServer() {
        BluetoothGattService UART_SERVICE = new BluetoothGattService(UARTProfile.UART_SERVICE,
                BluetoothGattService.SERVICE_TYPE_PRIMARY);

        BluetoothGattCharacteristic TX_READ_CHAR =
                new BluetoothGattCharacteristic(UARTProfile.TX_READ_CHAR,
                        //Read-only characteristic, supports notifications
                        BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                        BluetoothGattCharacteristic.PERMISSION_READ);

        //Descriptor for read notifications
        BluetoothGattDescriptor TX_READ_CHAR_DESC = new BluetoothGattDescriptor(UARTProfile.TX_READ_CHAR_DESC, 
                                                           UARTProfile.DESCRIPTOR_PERMISSION);
        TX_READ_CHAR.addDescriptor(TX_READ_CHAR_DESC);


        BluetoothGattCharacteristic RX_WRITE_CHAR =
                new BluetoothGattCharacteristic(UARTProfile.RX_WRITE_CHAR,
                        //write permissions
                        BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE);


        UART_SERVICE.addCharacteristic(TX_READ_CHAR);
        UART_SERVICE.addCharacteristic(RX_WRITE_CHAR);

        mGattServer.addService(UART_SERVICE);
    }

Once the services are added, we need to advertise them so clients can explore and make use of them. Create AdvertiseSettings (you can experiment with the values, I have used what works for me). The add the Services you want to advertise and start advertising.

    private void startAdvertising() {
        if (mBluetoothLeAdvertiser == null) return;

        AdvertiseSettings settings = new AdvertiseSettings.Builder()
                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
                .setConnectable(true)
                .setTimeout(0)
                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
                .build();

        AdvertiseData data = new AdvertiseData.Builder()
                .setIncludeDeviceName(true)
                .addServiceUuid(new ParcelUuid(UARTProfile.UART_SERVICE))
                .build();

        mBluetoothLeAdvertiser.startAdvertising(settings, data, mAdvertiseCallback);
    }

As you know BluetoothGattServerCallback is the one handles all the connections from clients and serves them as per need. There are many methods that we override to provide our own implementation. Below I have snippets for some important ones.

onConnectionStateChange is the one which gets called when the connection state with a client changes. We can handle them to keep a list of clients or initialize things when a client connects etc.

    private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
        //....

        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            super.onConnectionStateChange(device, status, newState);
            Log.i(TAG, "onConnectionStateChange "
                    +UARTProfile.getStatusDescription(status)+" "
                    +UARTProfile.getStateDescription(newState));

            if (newState == BluetoothProfile.STATE_CONNECTED) {
                postDeviceChange(device, true);

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                postDeviceChange(device, false);
            }
        }

       ....//

onCharacteristicWriteRequest is the method that gets called when a client writes to any characteristic on GattServer. Hence we need to check characteristic.getUuid() to find on which characteristic the client has operated on and respond accordingly. Here once I get the message, I send a success response and then handle our reply using method sendOurResponse.

        //...
        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device,
                                                 int requestId,
                                                 BluetoothGattCharacteristic characteristic,
                                                 boolean preparedWrite,
                                                 boolean responseNeeded,
                                                 int offset,
                                                 byte[] value) {
            super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite,
                                                responseNeeded, offset, value);
            Log.i(TAG, "onCharacteristicWriteRequest " + characteristic.getUuid().toString());

            if (UARTProfile.RX_WRITE_CHAR.equals(characteristic.getUuid())) {

                //IMP: Copy the received value to storage
                storage = value;
                if (responseNeeded) {
                    mGattServer.sendResponse(device,
                            requestId,
                            BluetoothGatt.GATT_SUCCESS,
                            0,
                            value);
                    Log.d(TAG, "Received  data on " + characteristic.getUuid().toString());
                    Log.d(TAG, "Received data" + bytesToHex(value));

                }

                //IMP: Respond
                sendOurResponse();

                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this, "We received data", Toast.LENGTH_SHORT).show();
                    }
                });


            }
        }
        //....

Since our responses are notifications. Clients need to set the Descriptor (UUID 0x2902) to get the response notifications. Hence on the server side we need to provide methods to read and write Descriptors. Remember descriptor write needs to have a response sent back with GATT_SUCCESS. So client knows. Else client assumes something has gone wrong and disconnects, usually throwing error 133 or 22. Its hard to debug.

       //......
        @Override
        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, 
                                   int offset, BluetoothGattDescriptor descriptor) {
            Log.d("HELLO", "Our gatt server descriptor was read.");
            super.onDescriptorReadRequest(device, requestId, offset, descriptor);
            Log.d("DONE", "Our gatt server descriptor was read.");
        }

        @Override
        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, 
BluetoothGattDescriptor descriptor, 
                        boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            Log.d("HELLO", "Our gatt server descriptor was written.");
            super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
            Log.d("DONE", "Our gatt server descriptor was written.");

            //NOTE: Its important to send response. It expects response else it will disconnect
            if (responseNeeded) {
                mGattServer.sendResponse(device,
                        requestId,
                        BluetoothGatt.GATT_SUCCESS,
                        0,
                        value);

            }

        }
       //....

Our last method is our own sendOurResponse. We we process the input and send the notification. Please note we use setValue and notifyCharacteristicChanged to send the notification to connected device.

    //Send notification to all the devices once you write
    private void sendOurResponse() {
        for (BluetoothDevice device : mConnectedDevices) {
            BluetoothGattCharacteristic readCharacteristic = mGattServer.getService(UARTProfile.UART_SERVICE)
                    .getCharacteristic(UARTProfile.TX_READ_CHAR);

            byte[] notify_msg = storage;
            String hexStorage = bytesToHex(storage);
            Log.d(TAG, "received string = " + bytesToHex(storage));


            if (hexStorage.equals("77686F616D69")) {

                notify_msg = "I am echo an machine".getBytes();

            } else if (bytesToHex(storage).equals("64617465")) {
                DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                Date date = new Date();
                notify_msg = dateFormat.format(date).getBytes();

            } else {
                //TODO: Do nothing send what you received. Basically echo
            }
            readCharacteristic.setValue(notify_msg);
            Log.d(TAG, "Sending Notifications" + notify_msg);
            boolean is_notified = mGattServer.notifyCharacteristicChanged(device, readCharacteristic, false);
            Log.d(TAG, "Notifications =" + is_notified);
        }
    }

This is not complete, of course you need to better handling of disconnect, shutdown and startup etc But this gives you an idea how easy it is to write GattServer on Android.

You can see all code on GitHub at release v1.0 of BleUARTPeripheral. The release also has prebuilt android app, its also on Playstore. Screenshots of the communication are below featuring NRF UARTApp as the client.

Simple UART GATT Server. Showing the clients connected to it.

Simple UART GATT Server. Showing the clients connected to it.

UART GATT Client App. Talking to Server.

UART GATT Client App. Talking to Server.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *