UART GATT Server (Peripheral) on Android

In most BLE scenarios, Android app is a client (GATT Client). But one can also use Android as a GATT Server. There are use-cases where running a GATT Server on Android can be useful. For example let's say you want a desktop app to display SMS notifications. It's easy to write a GATT server (on Phone) that pushes the message to Client (Desktop) as and when SMS arrives.

UART is the most popular protocol used for talking to a computer device over serial port. If we implement a UART GATT server, it should solve our problem. What I am talking here is not exactly UART in traditional sense. It’s an emulation of serial port over BLE. Its one of the best ways for implementing Android to Android or Android to Desktop communication over a simple protocol.

There are many UART client apps and libraries for mobiles, desktops. UART GATT Servers are usually written for sensors, tags etc. So in this how-to I am implementing an Android app - A GATT server that talks UART over BLE. You could use it for sending SMS alerts or do any other communication.

The UART over BLE Protocol flow.

The UART over BLE Protocol flow.

In the example code below I am writing a simple server, once connected it responds to commands like whoami or date etc. If it can't figure then it just echoes 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.

10 Responses

  1. Shrikant says:

    what is the defination of postDeviceChange in BluetoothGattServerCallback..

  2. Shrikant says:

    services are not going to be discovered in central going into else code for discoverServices() call with status 133 why?
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    if (status == BluetoothGatt.GATT_SUCCESS) {
    broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
    } else {
    Log.w(TAG, “onServicesDiscovered received: ” + status);
    }
    }

  3. Ganesh says:

    Very concise and clean code! Thanks for sharing!

  4. vivek marwal says:

    hello sir, i am continuously getting GATT SERVER ERROR 1. when start this app in my phone. my phone have android version 8.
    please help me.

  5. vivek marwal says:

    hello sir, i am getting continuously GATT SERVER ERROR 1. when app started. please help me.

    • sylweb says:

      Hello,
      For people reading this post and having the same problem, just replace “.setIncludeDeviceName(true)” by “.setIncludeDeviceName(false)”.
      The maximum size for advertising data is 31 bytes. On some device, including the device name, might make data too large.

  6. GaryD says:

    Hey Thej,

    Thank you for this posting. It is a lifesaver for me because I realized I needed an Android server based UART to rapidly develop a prototype and your example worked the first time. My application is for 5 Android phones connected to their respective light sensors that need to send reports to a central laptop application. Can I run as a server for the UART concurrently with running as a client to the light sensor? Or do I have to disconnect from my light sensor, advertise as a server to the laptop, connect, send data to the laptop, then disconnect with the laptop, and finally reconnect to the light sensor? Ideally, I would like to connect as a client to the sensor to get continuous updates, connect to the laptop as a server with notifications turned on, write a note once in a while in my user interface concerning the latest light level readings, then just post a message to the laptop with notification. The Laptop needs to connect up to 5 phones to monitor for any notes to be stored from any of the the active phone/sensor pairs.

  7. Bocha says:

    Thank you kind person! Your example helped me a lot to figure it out.

  8. ASMA says:

    Amazing article Thej

    Very helpful.

  1. November 20, 2017

    […] more posts. PS2: A more complicated picture of the BLE UART. PS3:I have implemented a rudimentary UART GATT server on Android. You might want to check it […]