diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java index 5b1b76e91..830626917 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTClient.java @@ -47,7 +47,13 @@ class PebbleGATTClient extends BluetoothGattCallback { private static final UUID CONNECTION_PARAMETERS_CHARACTERISTIC = UUID.fromString("00000005-328E-0FBB-C642-1AA6699BDADA"); private static final UUID CHARACTERISTIC_CONFIGURATION_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); - private final BluetoothDevice mBtDevice; + //PPoGATT service (Pebble side) + private static final UUID PPOGATT_SERVICE_UUID = UUID.fromString("30000003-328E-0FBB-C642-1AA6699BDADA"); + private static final UUID PPOGATT_CHARACTERISTIC_READ = UUID.fromString("30000004-328E-0FBB-C642-1AA6699BDADA"); + private static final UUID PPOGATT_CHARACTERISTIC_WRITE = UUID.fromString("30000006-328E-0FBB-C642-1AA6699BDADA"); + + private BluetoothGattCharacteristic writeCharacteristics; + private final Context mContext; private final PebbleLESupport mPebbleLESupport; @@ -58,9 +64,8 @@ class PebbleGATTClient extends BluetoothGattCallback { PebbleGATTClient(PebbleLESupport pebbleLESupport, Context context, BluetoothDevice btDevice) { mContext = context; - mBtDevice = btDevice; mPebbleLESupport = pebbleLESupport; - connectToPebble(mBtDevice); + connectToPebble(btDevice); } public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { @@ -72,6 +77,8 @@ class PebbleGATTClient extends BluetoothGattCallback { int newMTU = characteristic.getIntValue(FORMAT_UINT16, 0); LOG.info("Pebble requested MTU: " + newMTU); mPebbleLESupport.setMTU(newMTU); + } else if (characteristic.getUuid().equals(PPOGATT_CHARACTERISTIC_READ)) { + mPebbleLESupport.handlePPoGATTPacket(characteristic.getValue().clone()); } else { LOG.info("onCharacteristicChanged()" + characteristic.getUuid().toString() + " " + GB.hexdump(characteristic.getValue(), 0, -1)); } @@ -143,6 +150,12 @@ class PebbleGATTClient extends BluetoothGattCallback { } else if (CHARACTERISTICUUID.equals(CONNECTIVITY_CHARACTERISTIC)) { subscribeToMTU(gatt); } else if (CHARACTERISTICUUID.equals(MTU_CHARACTERISTIC)) { + if (mPebbleLESupport.clientOnly) { + subscribeToPPoGATT(gatt); + } else { + setMTU(gatt); + } + } else if (CHARACTERISTICUUID.equals(PPOGATT_CHARACTERISTIC_READ)) { setMTU(gatt); } } @@ -171,7 +184,11 @@ class PebbleGATTClient extends BluetoothGattCallback { // 2 - always 0 // 3 - unknown, set on kitkat (seems to help to get a "better" pairing) // 4 - unknown, set on some phones - characteristic.setValue(new byte[]{9}); + if (mPebbleLESupport.clientOnly) { + characteristic.setValue(new byte[]{0x11}); // needed in clientOnly mode (TODO: try 0x19) + } else { + characteristic.setValue(new byte[]{0x09}); // I just keep this, because it worked + } gatt.writeCharacteristic(characteristic); } else { LOG.info("This seems to be some <4.0 FW Pebble, reading pairing trigger"); @@ -239,6 +256,25 @@ class PebbleGATTClient extends BluetoothGattCallback { gatt.writeCharacteristic(characteristic); } + private void subscribeToPPoGATT(BluetoothGatt gatt) { + LOG.info("subscribing to PPoGATT read characteristic"); + BluetoothGattDescriptor descriptor = gatt.getService(PPOGATT_SERVICE_UUID).getCharacteristic(PPOGATT_CHARACTERISTIC_READ).getDescriptor(CHARACTERISTIC_CONFIGURATION_DESCRIPTOR); + descriptor.setValue(new byte[]{1, 0}); + gatt.writeDescriptor(descriptor); + gatt.setCharacteristicNotification(gatt.getService(PPOGATT_SERVICE_UUID).getCharacteristic(PPOGATT_CHARACTERISTIC_READ), true); + writeCharacteristics = gatt.getService(PPOGATT_SERVICE_UUID).getCharacteristic(PPOGATT_CHARACTERISTIC_WRITE); + } + + synchronized void sendDataToPebble(byte[] data) { + writeCharacteristics.setValue(data.clone()); + mBluetoothGatt.writeCharacteristic(writeCharacteristics); + } + + synchronized void sendAckToPebble(int serial) { + writeCharacteristics.setValue(new byte[]{(byte) (((serial << 3) | 1) & 0xff)}); + mBluetoothGatt.writeCharacteristic(writeCharacteristics); + } + public void close() { if (mBluetoothGatt != null) { mBluetoothGatt.disconnect(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java index 30b366b99..9edb87717 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java @@ -71,13 +71,12 @@ class PebbleGATTServer extends BluetoothGattServerCallback { } synchronized void sendDataToPebble(byte[] data) { - //LOG.info("send data to pebble " + GB.hexdump(data, 0, -1)); writeCharacteristics.setValue(data.clone()); mBluetoothGattServer.notifyCharacteristicChanged(mBtDevice, writeCharacteristics, false); } - synchronized private void sendAckToPebble(int serial) { + synchronized void sendAckToPebble(int serial) { writeCharacteristics.setValue(new byte[]{(byte) (((serial << 3) | 1) & 0xff)}); mBluetoothGattServer.notifyCharacteristicChanged(mBtDevice, writeCharacteristics, false); @@ -110,39 +109,7 @@ class PebbleGATTServer extends BluetoothGattServerCallback { LOG.warn("unexpected write request"); return; } - if (!mPebbleLESupport.mIsConnected) { - mPebbleLESupport.mIsConnected = true; - synchronized (mPebbleLESupport) { - mPebbleLESupport.notify(); - } - } - //LOG.info("write request: offset = " + offset + " value = " + GB.hexdump(value, 0, -1)); - int header = value[0] & 0xff; - int command = header & 7; - int serial = header >> 3; - if (command == 0x01) { - LOG.info("got ACK for serial = " + serial); - if (mPebbleLESupport.mPPAck != null) { - mPebbleLESupport.mPPAck.countDown(); - } else { - LOG.warn("mPPAck countdownlatch is not present but it probably should"); - } - } - if (command == 0x02) { // some request? - LOG.info("got command 0x02"); - if (value.length > 1) { - sendDataToPebble(new byte[]{0x03, 0x19, 0x19}); // no we don't know what that means - mPebbleLESupport.createPipedInputReader(); // FIXME: maybe not here - } else { - sendDataToPebble(new byte[]{0x03}); // no we don't know what that means - } - } else if (command == 0) { // normal package - LOG.info("got PPoGATT package serial = " + serial + " sending ACK"); - - sendAckToPebble(serial); - - mPebbleLESupport.writeToPipedOutputStream(value, 1, value.length - 1); - } + mPebbleLESupport.handlePPoGATTPacket(value); } public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java index 098d139d7..610f08788 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java @@ -39,8 +39,9 @@ public class PebbleLESupport { private PipedOutputStream mPipedOutputStream; private int mMTU = 20; private int mMTULimit = Integer.MAX_VALUE; - boolean mIsConnected = false; - CountDownLatch mPPAck; + public boolean clientOnly = false; // currently broken, and only possible for Pebble 2 + private boolean mIsConnected = false; + private CountDownLatch mPPAck; public PebbleLESupport(Context context, final BluetoothDevice btDevice, PipedInputStream pipedInputStream, PipedOutputStream pipedOutputStream) throws IOException { mBtDevice = btDevice; @@ -56,8 +57,10 @@ public class PebbleLESupport { mMTULimit = Math.max(mMTULimit, 20); mMTULimit = Math.min(mMTULimit, 512); - mPebbleGATTServer = new PebbleGATTServer(this, context, mBtDevice); - if (mPebbleGATTServer.initialize()) { + if (!clientOnly) { + mPebbleGATTServer = new PebbleGATTServer(this, context, mBtDevice); + } + if (clientOnly || mPebbleGATTServer.initialize()) { mPebbleGATTClient = new PebbleGATTClient(this, context, mBtDevice); try { synchronized (this) { @@ -73,11 +76,11 @@ public class PebbleLESupport { throw new IOException("connection failed"); } - void writeToPipedOutputStream(byte[] value, int offset, int count) { + private void writeToPipedOutputStream(byte[] value, int offset, int count) { try { mPipedOutputStream.write(value, offset, count); } catch (IOException e) { - LOG.warn("error writing to output stream"); + LOG.warn("error writing to output stream", e); } } @@ -101,7 +104,7 @@ public class PebbleLESupport { } } - synchronized void createPipedInputReader() { + private synchronized void createPipedInputReader() { if (mPipeReader == null) { mPipeReader = new PipeReader(); } @@ -126,6 +129,58 @@ public class PebbleLESupport { mMTU = Math.min(mtu, mMTULimit); } + public void handlePPoGATTPacket(byte[] value) { + if (!mIsConnected) { + mIsConnected = true; + synchronized (this) { + this.notify(); + } + } + //LOG.info("write request: offset = " + offset + " value = " + GB.hexdump(value, 0, -1)); + int header = value[0] & 0xff; + int command = header & 7; + int serial = header >> 3; + if (command == 0x01) { + LOG.info("got ACK for serial = " + serial); + if (mPPAck != null) { + mPPAck.countDown(); + } else { + LOG.warn("mPPAck countdownlatch is not present but it probably should"); + } + } + if (command == 0x02) { // some request? + LOG.info("got command 0x02"); + if (value.length > 1) { + sendDataToPebble(new byte[]{0x03, 0x19, 0x19}); // no we don't know what that means + createPipedInputReader(); // FIXME: maybe not here + } else { + sendDataToPebble(new byte[]{0x03}); // no we don't know what that means + } + } else if (command == 0) { // normal package + LOG.info("got PPoGATT package serial = " + serial + " sending ACK"); + + sendAckToPebble(serial); + + writeToPipedOutputStream(value, 1, value.length - 1); + } + } + + private void sendAckToPebble(int serial) { + if (mPebbleGATTServer != null) { + mPebbleGATTServer.sendAckToPebble(serial); + } else { + mPebbleGATTClient.sendAckToPebble(serial); + } + } + + private void sendDataToPebble(byte[] bytes) { + if (mPebbleGATTServer != null) { + mPebbleGATTServer.sendDataToPebble(bytes); + } else { + mPebbleGATTClient.sendDataToPebble(bytes); + } + } + private class PipeReader extends Thread { int mmSequence = 0; @@ -159,7 +214,7 @@ public class PebbleLESupport { byte[] outBuf = new byte[chunkSize + 1]; outBuf[0] = (byte) ((mmSequence++ << 3) & 0xff); System.arraycopy(buf, srcPos, outBuf, 1, chunkSize); - mPebbleGATTServer.sendDataToPebble(outBuf); + sendDataToPebble(outBuf); srcPos += chunkSize; payloadToSend -= chunkSize; }