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?