This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Connection between Android phone and nRF52

Hello,

I'm having problems getting a stable channel from an Android device (LG K8) and the nRF52 development board. It is hard to tell whether which side is causing the problems. What happens is that one of the side believes that the data is sent, but it seems like it wasn't sent. I have seen this problem from both sides. We are simulating a UART over BLE (very similar to the nordic UART profile). I.e., we are using notifications and write without response.

The channel can be stable, and then it stops working and will not start work again before Bluetooth is restarted on the Android device. When investigating this i decided to enable the HCI snoop log and i can find some interesting stuff here.

Some notes about the android code:

  • I always wait for the corresponding gatt callback before performing a new gatt command.
  • The callbacks does not block but instead adds the next task to be handled by another thread. Inspiration from this document: droidcon.de/.../practical_bluetooth_le_on_android_0.pdf
  • Scanning is disabled when a connection is to be initiated.

This is the log from the application:

09-06 13:45:21.222 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_CONNECT
09-06 13:45:21.222 9507-11481/test.app D/BluetoothGatt: connect() - device: F6:C5:9D:24:12:0A, auto: false
09-06 13:45:21.222 9507-11481/test.app D/BluetoothGatt: registerApp()
09-06 13:45:21.222 9507-11481/test.app D/BluetoothGatt: registerApp() - UUID=78c2d4f1-e459-456f-993f-9e2667ad5c95
09-06 13:45:21.223 9507-9616/test.app D/BluetoothGatt: onClientRegistered() - status=0 clientIf=5
09-06 13:45:21.548 9507-9519/test.app D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=5 device=F6:C5:9D:24:12:0A
09-06 13:45:21.548 9507-9519/test.app I/BleClientChannel: onConnectionStateChange, status 0, newState 2
09-06 13:45:21.548 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_NEGOTIATE_MTU
09-06 13:45:21.549 9507-11481/test.app D/BluetoothGatt: configureMTU() - device: F6:C5:9D:24:12:0A mtu: 247
09-06 13:45:21.784 9507-9518/test.app D/BluetoothGatt: onConfigureMTU() - Device=F6:C5:9D:24:12:0A mtu=247 status=0
09-06 13:45:21.784 9507-9518/test.app I/BleClientChannel: onMtuChanged, status: 0, mtu: 247
09-06 13:45:21.784 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_DISCOVER_SERVICES
09-06 13:45:21.784 9507-11481/test.app D/BluetoothGatt: discoverServices() - device: F6:C5:9D:24:12:0A
09-06 13:45:21.793 9507-9519/test.app D/BluetoothGatt: onSearchComplete() = Device=F6:C5:9D:24:12:0A Status=0
09-06 13:45:21.793 9507-9519/test.app I/BleClientChannel: onServicesDiscovered, status: 0
09-06 13:45:21.793 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_VERIFY_SERVICES
09-06 13:45:21.794 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_ENABLE_NOTIFICATIONS
09-06 13:45:21.794 9507-11481/test.app D/BluetoothGatt: setCharacteristicNotification() - uuid: 48f058af-2d79-41af-be4f-722dee1ab571 enable: true
09-06 13:45:21.834 9507-9518/test.app I/BleClientChannel: onDescriptorWrite, status: 0
09-06 13:45:21.835 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_NOTIFICATIONS_ENABLED
09-06 13:45:21.840 9507-11134/test.app I/BleClientChannel: Writing
09-06 13:45:21.840 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_SEND_DATA
09-06 13:45:21.843 9507-9616/test.app I/BleClientChannel: onCharacteristicWrite, status: 0
09-06 13:45:21.843 9507-9616/test.app I/BleClientChannel: cha.getValue(): 2a00000053437632010000000000e87ac51c25f2c15f044bb9609eac34b42341b35ca5ac1244806a544345f47f7c
09-06 13:45:21.843 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_DATA_SENT
09-06 13:45:21.844 9507-11134/test.app I/BleClientChannel: write done
09-06 13:45:21.844 9507-11134/test.app I/BleClientChannel: read enter
09-06 13:45:21.983 9507-9616/test.app I/BleClientChannel: onCharacteristicChanged
09-06 13:45:21.983 9507-9616/test.app I/BleClientChannel: cha.getValue(): 26000000020000000000b54cda9af965c35188cf9fed036eef70e36523f8696b9daa4e22f2fb6e039d05
09-06 13:45:21.984 9507-11134/test.app I/BleClientChannel: Read done
09-06 13:45:21.986 9507-11134/test.app I/BleClientChannel: read enter
09-06 13:45:21.987 9507-9518/test.app I/BleClientChannel: onCharacteristicChanged
09-06 13:45:21.987 9507-9518/test.app I/BleClientChannel: cha.getValue(): 7800000006004f79e270c2d317c7eecc2fb6e720175e9360d887a0b189823596306b3d65de2a7a7424a015664f527568807b492a6a43e123a4b9aa379eae94f9c01be289c172bc6fdd857f1c452971d644b10d0f309c3368f8c0d015e3d5d7a987041cec70e00656ccdfba6775d4eeadae2529c9b7ae5ce37b21cd67
09-06 13:45:21.987 9507-11134/test.app I/BleClientChannel: Read done
09-06 13:45:21.990 9507-11134/test.app I/BleClientChannel: Writing
09-06 13:45:21.990 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_SEND_DATA
09-06 13:45:21.991 9507-9616/test.app I/BleClientChannel: onCharacteristicWrite, status: 0
09-06 13:45:21.991 9507-9616/test.app I/BleClientChannel: cha.getValue(): 780000000600ec761d5f10f6263afba2242666bd115f454e61881215b333747074974edef3d85f1f75c93d112d681fa5b6278ff8f1b49827e398f358b248dce414cd4ee3c91c5a19b54ef38f5aa94cfb4ab48359cb7ba808b3f7ea3a09af2cded0a79f26902e661b124432b0fc97cc173b756bd71f84e483eaf4c204
09-06 13:45:21.992 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_DATA_SENT
09-06 13:45:21.992 9507-11134/test.app I/BleClientChannel: write done
09-06 13:45:21.993 9507-11496/test.app I/BleClientChannel: read enter
09-06 13:45:21.993 9507-11497/test.app I/BleClientChannel: Writing
09-06 13:45:21.993 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_SEND_DATA
09-06 13:45:21.996 9507-9518/test.app I/BleClientChannel: onCharacteristicWrite, status: 0
09-06 13:45:21.996 9507-9518/test.app I/BleClientChannel: cha.getValue(): 3c0000000600caf11f37ad3099a41c70445fd69f035ded8e2edb4507fce5c30de5ace5de4480731edaee8f9f5ec91162933e60b25c5245592ff55af8aed4f11d
09-06 13:45:21.996 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_DATA_SENT
09-06 13:45:21.999 9507-11497/test.app I/BleClientChannel: write done
09-06 13:45:25.993 9507-11134/test.app I/BleClientChannel: close enter
09-06 13:45:25.993 9507-11134/test.app I/BleClientChannel: Close handeled
09-06 13:45:25.993 9507-11481/test.app I/BleClientChannel: task to handle: GATT_TASK_DISCONNECT
09-06 13:45:25.993 9507-11481/test.app D/BluetoothGatt: cancelOpen() - device: F6:C5:9D:24:12:0A
09-06 13:45:25.998 9507-9519/test.app D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=5 device=F6:C5:9D:24:12:0A
09-06 13:45:25.998 9507-9519/test.app I/BleClientChannel: onConnectionStateChange, status 0, newState 0

The connection seems to be broken after the second onCharacteristicChanged callback. However, no gatt callback are received here and gatt.writeCharateristics does not return any error. Further, the onCharacteristicWrite are recevied. After his i try to send more data. None of these packages are received on the nRF52 side.

When i pulled the HCI snoop log i can only see events until the last onCharacteristicChanged. The next after this is a disconnect that is sent from the host to the controller. This happends when there is no response in 4 seconds, then we disconnect and consider this as a timeout.

Here is a copy from this session when opening the HCI snoop log with wireshark.

4204    132.134644  controller  HCI_CMD 6   host    Sent LE Set Scan Enable
4205    132.139460  host    HCI_EVT 7   controller  Rcvd Command Complete (LE Set Scan Enable)
4206    132.139506  controller  HCI_CMD 11  host    Sent LE Set Scan Parameters
4207    132.176921  host    HCI_EVT 7   controller  Rcvd Command Complete (LE Set Scan Parameters)
4208    132.177347  controller  HCI_CMD 7   host    Sent Vendor Command 0x0157 (opcode 0xFD57)
4209    132.179084  host    HCI_EVT 10  controller  Rcvd Command Complete (Vendor Command 0x0157 [opcode 0xFD57])
4210    132.186310  controller  HCI_CMD 29  host    Sent LE Create Connection
4211    132.187865  host    HCI_EVT 7   controller  Rcvd Command Status (LE Create Connection)
4212    132.463131  host    HCI_EVT 22  controller  Rcvd LE Meta (LE Connection Complete)
4213    132.463485  controller  HCI_CMD 6   host    Sent LE Read Remote Used Features
4214    132.465962  host    HCI_EVT 7   controller  Rcvd Command Status (LE Read Remote Used Features)
4215    132.494882  LgElectr_2e:f2:11 (LG K8 4G)    ATT 12  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    Rcvd Exchange MTU Request, Client Rx MTU: 247
4216    132.496022  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    ATT 12  LgElectr_2e:f2:11 (LG K8 4G)    Sent Exchange MTU Response, Server Rx MTU: 247
4217    132.595092  host    HCI_EVT 12  controller  Rcvd Number of Completed Packets
4218    132.595628  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    ATT 12  LgElectr_2e:f2:11 (LG K8 4G)    Sent Exchange MTU Request, Client Rx MTU: 247
4219    132.744603  LgElectr_2e:f2:11 (LG K8 4G)    ATT 12  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    Rcvd Exchange MTU Response, Server Rx MTU: 247
4220    132.759773  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    ATT 14  LgElectr_2e:f2:11 (LG K8 4G)    Sent Write Request, Handle: 0x0013 (Unknown: Unknown: Client Characteristic Configuration)
4221    132.795599  LgElectr_2e:f2:11 (LG K8 4G)    ATT 10  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    Rcvd Write Response, Handle: 0x0013 (Unknown: Unknown: Client Characteristic Configuration)
4222    132.797135  host    HCI_EVT 12  controller  Rcvd Number of Completed Packets
4223    132.804275  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    ATT 58  LgElectr_2e:f2:11 (LG K8 4G)    Sent Write Command, Handle: 0x0010 (Unknown: Unknown)
4224    132.944446  LgElectr_2e:f2:11 (LG K8 4G)    HCI_ACL 32  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    Rcvd  [Reassembled in #4225]
4225    132.945044  LgElectr_2e:f2:11 (LG K8 4G)    ATT 27  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    Rcvd Handle Value Notification, Handle: 0x0012 (Unknown: Unknown)
4226    132.946419  LgElectr_2e:f2:11 (LG K8 4G)    HCI_ACL 32  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    Rcvd  [Reassembled in #4230]
4227    132.946525  LgElectr_2e:f2:11 (LG K8 4G)    HCI_ACL 32  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    Rcvd  [Continuation to #4226]
4228    132.947045  LgElectr_2e:f2:11 (LG K8 4G)    HCI_ACL 32  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    Rcvd  [Continuation to #4226]
4229    132.948283  LgElectr_2e:f2:11 (LG K8 4G)    HCI_ACL 32  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    Rcvd  [Continuation to #4226]
4230    132.948706  LgElectr_2e:f2:11 (LG K8 4G)    ATT 28  f6:c5:9d:24:12:0a (PoTAa69db5d5LOCK)    Rcvd Handle Value Notification, Handle: 0x0012 (Unknown: Unknown)
4231    136.956180  controller  HCI_CMD 7   host    Sent Disconnect
4232    136.959145  host    HCI_EVT 7   controller  Rcvd Command Status (Disconnect)
4233    136.993523  controller  HCI_CMD 6   host    Sent Vendor Command 0x0157 (opcode 0xFD57)
4234    137.000253  host    HCI_EVT 7   controller  Rcvd Disconnect Complete
4235    137.000316  host    HCI_EVT 9   controller  Rcvd Command Complete (Vendor Command 0x0157 [opcode 0xFD57])

So, it appears that all gatt commands after the last onCharacteristicChanged callback are ignored. So, my guess is that this is an android issue. However, i need to know how i can approach this issue, where do i start?

Here is the android code:

package test.androidlib;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;


import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicBoolean;

import test.common.ToWaitFor;
import test.common.cut.Hex;
import saltchannel.ByteChannel;
import saltchannel.ComException;
import saltchannel.StreamChannel;

/**
 * The client-side of a BLE channel session.
 *
 */
public class BleClientChannel implements ByteChannel, Handler.Callback {

    private final String TAG = "BleClientChannel";
    private static final int MAX_NUM_CONNECTION_RETRIES = 0;
    private static final int MAX_MTU_SIZE = 247;

    private Context context;
    private LogOutput logOutput;
    private Listener listener;
    private long timeout = 6000;
    private final Object stateLock = new Object();

    private final AtomicBoolean connectCalled = new AtomicBoolean(false);
    private final AtomicBoolean isClosed = new AtomicBoolean(false);
    private HandlerThread handlerThread;
    private Handler bleHandler;
    private BluetoothDevice mDevice;
    private BluetoothGatt mGatt;
    private BluetoothGattCharacteristic inCha;
    private BluetoothGattCharacteristic outCha;

    private int effectiveMtu = 20;
    private int connectionRetries = 0;
    private GattEvent connectedEvent;
    private GattEvent writeEvent;
    private GattEvent disconnectedEvent;
    private byte[] nextPacketToSend;

    private CircularByteBuffer circularByteBuffer;
    private ByteChannel byteChannel;
    private OutputStream bleOutputStream;
    private BluetoothManager manager;

    public BleClientChannel(Context context) {
        this.context = context;
        this.logOutput = new LogOutput() {

            @Override
            public void print(String s) {
                Log.i(TAG, s);
            }
        };
        this.listener = new Listener() {

            @Override
            public void disconnected() {
                log("Disconnected");
            }
        };

        this.handlerThread = new HandlerThread("BLE-Worker");

        manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);


    }

    public void initLog(LogOutput logOutput) {
        this.logOutput = logOutput;
    }

    public void initListener(BleClientChannel.Listener listener) {
        this.listener = listener;
    }

    public void initTimeout(int millis) {
        this.timeout = millis;
    }

    /**
     * Connects to the device.
     *
     * @param device The BLE device to connect to.
     * @throws IllegalStateException if a connect attempt has been done with this object already.
     * @throws IOException if the connection could not be established.
     *
     */
    public void connect(BluetoothDevice device) throws IOException {


        if (connectCalled.get()) {
            connectCalled.set(true);
            throw new IllegalStateException("connect() have already been called");
        }

        Log.i("BleClientChannel", "Connected devices");
        for (BluetoothDevice tmpDevice : manager.getConnectedDevices(BluetoothProfile.GATT)) {
            // TODO: Verify not connected already
            Log.i(TAG, tmpDevice.getAddress());
        }


        this.mDevice = device;
        connectedEvent = new GattEvent();

        // Start BLE Worker thread
        handlerThread.start();
        bleHandler = new Handler(handlerThread.getLooper(), this);
        addTask(MyGattTask.GattTask.GATT_TASK_CONNECT);

        connectedEvent.waitForIt(timeout);

        if (!connectedEvent.isOk) {
            closeHard(true);
            throw new IOException(connectedEvent.message);
        }

        connectedEvent = null;

        this.circularByteBuffer = new CircularByteBuffer(100*1000, false);
        createBleStreamChannel();

    }

    public void close() {

        log("close enter");

        if (isClosed.get()) {
            isClosed.set(true);
            return;
        }

        log("Close handeled");

        if (connectedEvent != null) {
            connectedEvent.isOk = false;
            connectedEvent.message = "Canceled by user";
            connectedEvent.reportHappened();
        }

        synchronized (stateLock) {
        if (bleHandler != null) {

            disconnectedEvent = new GattEvent();
            bleHandler.removeMessages(0);
            addTask(MyGattTask.GattTask.GATT_TASK_DISCONNECT);
            disconnectedEvent.waitForIt(200);

        }
        }

        closeHard(false);

        log("close done");

    }

    private void closeHard(boolean reallyHard) {

        log("Closing hard, reallyHard: " + reallyHard);

        if (reallyHard) {
            if (isClosed.get()) {
                isClosed.set(true);
                return;
            }
        }

        synchronized (stateLock) {
            if (bleHandler != null) {
                bleHandler.removeCallbacksAndMessages(null);
                bleHandler.getLooper().quit();
                bleHandler = null;
            }

            if (this.mGatt != null) {
                this.mGatt.disconnect();
                this.mGatt.close();
                this.mGatt = null;
                new Thread(new Runnable() {

                    @Override
                    public void run() {
                        listener.disconnected();
                    }
                }).start();
            }

            if (byteChannel != null) {
                try {
                    circularByteBuffer.getInputStream().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    private void addTask(MyGattTask.GattTask task) {

        if (bleHandler != null) {
            if (bleHandler.getLooper().getThread().isAlive()) {
                Message msg = bleHandler.obtainMessage();
                msg.what = 0;
                msg.obj = new MyGattTask(task);
                msg.sendToTarget();
            }
        }
    }

    private void handleInternalError(final String error) {

        log("Internal error: " + error);

        /*
         * This function is called from the bluetoothGattCallback, due to the syncronized block
         * this method might block for a while. One should never block bluetoothGattCallback.
         * Therefore, a new thread/runnable is started.
         */
        new Thread(new Runnable() {

            @Override
            public void run() {
                synchronized (stateLock) {
                    if (writeEvent != null) {
                        writeEvent.isOk = false;
                        writeEvent.message = error;
                        writeEvent.reportHappened();
                    }
                    if (connectedEvent != null) {
                        connectedEvent.message = error;
                        connectedEvent.isOk = false;
                        connectedEvent.reportHappened();
                    }
                    closeHard(true);
                }
            }
        }).start();

    }

    @Override
    public byte[] read() throws ComException {
        log("read enter");

        if (isClosed.get()) {
            throw new ComException("Channel closed");
        }

        if (this.byteChannel != null) {
            byte[] tmp = this.byteChannel.read();
            log("Read done");
            return tmp;
        } else {
            throw new ComException("No byte channel available.");
        }

    }

    @Override
    public void write(byte[]... bytes) throws ComException {
        if (this.byteChannel != null) {
            this.byteChannel.write(bytes);
        } else {
            throw new ComException("No byte channel available.");
        }
    }

    @Override
    public boolean handleMessage(Message msg) {

        boolean ok = true;
        String errorMsg = "";
        MyGattTask.GattTask task = ((MyGattTask) msg.obj).task;

        log("task to handle: " + task);

        switch (task) {
            case GATT_TASK_CONNECT:
                this.mGatt = this.mDevice.connectGatt(this.context, false, mGattCallback);
                break;
            case GATT_TASK_RETRY_CONNECT:
                this.mGatt.close();
                this.mGatt = this.mDevice.connectGatt(this.context, false, mGattCallback);
                break;
            case GATT_TASK_NEGOTIATE_MTU:
                ok = this.mGatt.requestMtu(MAX_MTU_SIZE);
                errorMsg = "requestMtu() unexpectedly returned false";
                break;
            case GATT_TASK_DISCOVER_SERVICES:
                ok = this.mGatt.discoverServices();
                errorMsg = "discoverServices() unexpectedly returned false";
                break;
            case GATT_TASK_VERIFY_SERVICES:
                try {
                    setupTestService();
                    addTask(MyGattTask.GattTask.GATT_TASK_ENABLE_NOTIFICATIONS);
                } catch (IllegalStateException e) {
                    ok = false;
                    errorMsg = e.toString();
                }
                break;
            case GATT_TASK_ENABLE_NOTIFICATIONS:
                try {
                    enableNotifications();
                } catch (IllegalStateException e) {
                    errorMsg = e.toString();
                    ok = false;
                }
                break;
            case GATT_TASK_NOTIFICATIONS_ENABLED:
                if (connectedEvent != null) {
                    connectedEvent.isOk = true;
                    connectedEvent.reportHappened();
                }
                break;
            case GATT_TASK_SEND_DATA:
                if (nextPacketToSend != null) {
                    inCha.setValue(nextPacketToSend);
                    ok = this.mGatt.writeCharacteristic(inCha);
                    errorMsg = "writeCharacteristic() unexpectedly returned false";
                }
                break;
            case GATT_TASK_DATA_SENT:
                if (writeEvent != null) {
                    writeEvent.isOk = true;
                    writeEvent.reportHappened();
                }
                break;
            case GATT_TASK_DISCONNECT:
                this.mGatt.disconnect();
                break;
            case GATT_TASK_DISCONNECTED:
                break;
            default:
                log("Unhandled event");
                break;
        }

        if (!ok) {
            handleInternalError(errorMsg);
        }

        return ok;
    }

    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {

            log(String.format("onConnectionStateChange, status %d, newState %d", status, newState));


            if (status != BluetoothGatt.GATT_SUCCESS) {

                if (BleClientChannel.this.connectionRetries < MAX_NUM_CONNECTION_RETRIES && status == 133) {
                    // Status 133 seems to be recoverable, we try to reconnect here
                    BleClientChannel.this.connectionRetries++;
                    addTask(MyGattTask.GattTask.GATT_TASK_RETRY_CONNECT);
                } else {
                    handleInternalError("status != GATT_SUCCESS in onConnectionStateChange");
                }
                handleInternalError("status != GATT_SUCCESS in onConnectionStateChange");

                return;
            }

            if (newState == BluetoothProfile.STATE_CONNECTED) {
                addTask(MyGattTask.GattTask.GATT_TASK_NEGOTIATE_MTU);
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                if (disconnectedEvent != null) {
                    disconnectedEvent.isOk = true;
                    disconnectedEvent.reportHappened();
                }
                handleInternalError("Disconnected by peer");
            }
        }
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            log(String.format("onMtuChanged, status: %d, mtu: %d", status, mtu));
            if (status != BluetoothGatt.GATT_SUCCESS) {
                handleInternalError("status != GATT_SUCCESS in onMtuChanged");
                return;
            }

            if (mtu <= MAX_MTU_SIZE) {
                effectiveMtu = mtu - 3;
            } else {
                effectiveMtu = 23;
            }

            addTask(MyGattTask.GattTask.GATT_TASK_DISCOVER_SERVICES);

        }
        public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
            log(String.format("onServicesDiscovered, status: %d", status));
            if (status != BluetoothGatt.GATT_SUCCESS) {
                handleInternalError("status != GATT_SUCCESS in onServicesDiscovered");
                return;
            }
            addTask(MyGattTask.GattTask.GATT_TASK_VERIFY_SERVICES);

        }
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            log(String.format("onDescriptorWrite, status: %d", status));

            if (status != BluetoothGatt.GATT_SUCCESS) {
                handleInternalError("status != GATT_SUCCESS in onDescriptorWrite");
                return;
            }

            addTask(MyGattTask.GattTask.GATT_TASK_NOTIFICATIONS_ENABLED);
        }
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            // A message (chunk) was sent to the BLE device, now we can proceed sending next
            log(String.format("onCharacteristicWrite, status: %d", status));
            log("cha.getValue(): " + Hex.create(characteristic.getValue()));
            if (status != BluetoothGatt.GATT_SUCCESS) {
                handleInternalError("status != GATT_SUCCESS in onCharacteristicWrite");
                return;
            }
            addTask(MyGattTask.GattTask.GATT_TASK_DATA_SENT);
        }

        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic cha) {
            // Data was sent from BLE device
            log("onCharacteristicChanged");
            if (BleTestServer.OUT_UUID.equals(cha.getUuid())) {
                byte[] bytes = cha.getValue();
                log("cha.getValue(): " + Hex.create(bytes));
                try {
                    circularByteBuffer.getOutputStream().write(bytes, 0, bytes.length);
                } catch (IOException e) {
                    handleInternalError("onCharacteristicChanged, Could not add data to outputStream");
                }
            } else {
                log("onCharacteristicChanged, Unexpected characteristic");
            }
        }
    };

    private void enableNotifications() throws IllegalStateException {
        boolean ok;
        ok = this.mGatt.setCharacteristicNotification(outCha, true);

        if (!ok) {
            throw new IllegalStateException("gatt.setCharacteristicNotification unexpectedly returned false");
        }
        BluetoothGattDescriptor descriptor = outCha.getDescriptor(BleUtil.CLIENT_CHARACTERISTIC_CONFIG);
        ok = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        if (!ok) {
            throw new IllegalStateException("descriptor.setValue() unexpectedly returned false");
        }

        ok = this.mGatt.writeDescriptor(descriptor);
        if (!ok) {
            throw new IllegalStateException("gatt.writeDescriptor() unexpectedly returned false");
        }
    }

    private void setupTestService() throws IllegalStateException {

        BluetoothGattService service = this.mGatt.getService(BleTestServer.SERVICE_UUID);
        if (service == null) {
            throw new IllegalStateException("No Test service found");
        }

        inCha(service);
        outCha(service);

    }

    private void inCha(BluetoothGattService service) throws IllegalStateException {
        inCha = service.getCharacteristic(BleTestServer.IN_UUID);

        if (inCha == null) {
            throw new IllegalStateException("No in characteristics found");
        }

        int properties = inCha.getProperties();

        if (properties != BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) {
            throw new IllegalStateException("unexpected in characteristics properties, " + properties);
        }
    }

    private void outCha(BluetoothGattService service) throws IllegalStateException {
        outCha = service.getCharacteristic(BleTestServer.OUT_UUID);
        if (outCha == null) {
            throw new IllegalStateException("No out characteristics found");
        }

        int properties = outCha.getProperties();
        if (properties != BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
            throw new IllegalStateException("unexpected outCha properties, " + properties);
        }

    }


    private void createBleStreamChannel() {
        createBleOutputStream();

        byteChannel = new StreamChannel(circularByteBuffer.getInputStream(), bleOutputStream);

    }

    private void createBleOutputStream() {
        bleOutputStream = new OutputStream() {

            @Override
            public void write(int oneByte) throws IOException {
                write(new byte[]{(byte) oneByte}, 0, 1);
            }

            public void write(byte[] bytes) throws IOException {
                write(bytes, 0, bytes.length);
            }

            public void write(byte[] bytes, int initialOffset, int length) throws IOException {

                int bytesLeft = length;
                int offset = initialOffset;

                byte[] tempBytes = new byte[bytes.length - 4];
                System.arraycopy(bytes, 4, tempBytes, 0, length-4);

                log("Writing");

                while (bytesLeft > 0) {

                    if (isClosed.get()) {
                        throw new IOException("BLE Output stream is closed");
                    }

                    int packetSize = effectiveMtu;
                    if (bytesLeft < effectiveMtu) {
                        packetSize = bytesLeft;
                    }

                    byte[] packet = new byte[packetSize];
                    System.arraycopy(bytes, offset, packet, 0, packetSize);

                    nextPacketToSend = packet;

                    writeEvent = new GattEvent();
                    addTask(MyGattTask.GattTask.GATT_TASK_SEND_DATA);
                    writeEvent.waitForIt(timeout);
                    if (writeEvent.hasHappened()) {
                        if (!writeEvent.isOk) {
                            throw new IOException(writeEvent.message);
                        }
                    }
                    writeEvent = null;

                    bytesLeft -= packetSize;
                    offset += packetSize;

                }

                log("write done");
            }
        };
    }

    private void log(String s) {
        this.logOutput.print(s);
    }

    /**
     * Event happened when channel has been established,
     * when a timeout occurred, or when an error occurred.
     */
    private static class GattEvent extends ToWaitFor {

        public String message = "";
        public String type = "";
        public boolean isOk = false;

        @Override
        public boolean waitForIt(long timeout) {
            boolean happened = super.waitForIt(timeout);
            if (!happened) {
                type = "timeout";
                message = "timeout occurred";
                isOk = false;
            }

            return happened;
        }
    }

    /**
     * Listens to event that happens to the BLE client.
     */
    public interface Listener {
        /**
         * Reports the event that the client was disconnected.
         */
        void disconnected();
    }
}

Any suggestions?

Related