From 4a2ca2d94c827566f8682e8dbd6fbdf17d70b4dd Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Sun, 4 Jan 2015 23:48:53 +0100 Subject: o Adding a way to prepend callbacks on the promise's queue. Needed for devices that give you [disconnect, connect] events when connecting. Yay. o Reading meta data from the Soil Moisture device. --- .../io/trygvis/android/bt/BtActionExecutor.java | 356 ------------------ .../java/io/trygvis/android/bt/BtCallback.java | 20 +- .../main/java/io/trygvis/android/bt/BtDevice.java | 21 +- .../main/java/io/trygvis/android/bt/BtPromise.java | 404 +++++++++++++++++++++ .../io/trygvis/android/bt/DefaultBtService.java | 2 +- .../soilmoisture/DefaultSoilMoistureService.java | 69 +++- .../java/io/trygvis/soilmoisture/MainActivity.java | 10 +- .../java/io/trygvis/soilmoisture/SmDevice.java | 113 ++++++ .../java/io/trygvis/soilmoisture/SmSensor.java | 9 +- app/src/main/res/layout/listitem_device.xml | 9 + 10 files changed, 626 insertions(+), 387 deletions(-) delete mode 100644 app/src/main/java/io/trygvis/android/bt/BtActionExecutor.java create mode 100644 app/src/main/java/io/trygvis/android/bt/BtPromise.java diff --git a/app/src/main/java/io/trygvis/android/bt/BtActionExecutor.java b/app/src/main/java/io/trygvis/android/bt/BtActionExecutor.java deleted file mode 100644 index 984c738..0000000 --- a/app/src/main/java/io/trygvis/android/bt/BtActionExecutor.java +++ /dev/null @@ -1,356 +0,0 @@ -package io.trygvis.android.bt; - -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; -import android.util.Log; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Queue; - -import io.trygvis.android.F2; -import io.trygvis.android.Function; - -import static io.trygvis.android.bt.BtActionExecutor.EventType.onCharacteristicChanged; -import static io.trygvis.android.bt.BtActionExecutor.EventType.onCharacteristicRead; -import static io.trygvis.android.bt.BtActionExecutor.EventType.onCharacteristicWrite; -import static io.trygvis.android.bt.BtActionExecutor.EventType.onConnectionStateChange; -import static io.trygvis.android.bt.BtActionExecutor.EventType.onDescriptorRead; -import static io.trygvis.android.bt.BtActionExecutor.EventType.onDescriptorWrite; -import static io.trygvis.android.bt.BtActionExecutor.EventType.onFailure; -import static io.trygvis.android.bt.BtActionExecutor.EventType.onFinally; -import static io.trygvis.android.bt.BtActionExecutor.EventType.onReliableWriteCompleted; -import static io.trygvis.android.bt.BtActionExecutor.EventType.onServicesDiscovered; - -public class BtActionExecutor { - private final static String TAG = BtActionExecutor.class.getSimpleName(); - private final Queue actionQ = new ArrayDeque<>(); - private final Queue failureQ = new ArrayDeque<>(); - private final Queue finallyQ = new ArrayDeque<>(); - - public synchronized BtActionExecutor onConnectionStateChange(F2 callback) { - actionQ.add(new BtCallback("onConnectionStateChange") { - @Override - public boolean onConnectionStateChange(BluetoothGatt gatt, int newState) { - return callback.apply(gatt, newState); - } - }); - return this; - } - - public synchronized BtActionExecutor onServicesDiscovered(Function callback) { - actionQ.add(new BtCallback("onServicesDiscovered") { - @Override - public boolean onServicesDiscovered(BluetoothGatt gatt) { - return callback.apply(gatt); - } - }); - return this; - } - - public synchronized BtActionExecutor onCharacteristicRead(F2 callback) { - actionQ.add(new BtCallback("onCharacteristicRead") { - @Override - public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - return callback.apply(gatt, characteristic); - } - }); - return this; - } - - public synchronized BtActionExecutor onCharacteristicWrite(F2 callback) { - actionQ.add(new BtCallback("onCharacteristicWrite") { - @Override - public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - return callback.apply(gatt, characteristic); - } - }); - return this; - } - - public synchronized BtActionExecutor onCharacteristicChanged(F2 callback) { - actionQ.add(new BtCallback("onCharacteristicChanged") { - @Override - public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - return callback.apply(gatt, characteristic); - } - }); - return this; - } - - public synchronized BtActionExecutor onDescriptorRead(F2 callback) { - actionQ.add(new BtCallback("onDescriptorRead") { - @Override - public boolean onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { - return callback.apply(gatt, descriptor); - } - }); - return this; - } - - public synchronized BtActionExecutor onDescriptorWrite(F2 callback) { - actionQ.add(new BtCallback("onDescriptorWrite") { - @Override - public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { - return callback.apply(gatt, descriptor); - } - }); - return this; - } - - public synchronized BtActionExecutor onReliableWriteCompleted(Function callback) { - actionQ.add(new BtCallback("onReliableWriteCompleted") { - @Override - public boolean onReliableWriteCompleted(BluetoothGatt gatt) { - return callback.apply(gatt); - } - }); - return this; - } - - public synchronized BtActionExecutor onReadRemoteRssi(F2 callback) { - actionQ.add(new BtCallback("onReadRemoteRssi") { - @Override - public boolean onReadRemoteRssi(BluetoothGatt gatt, int rssi) { - return callback.apply(gatt, rssi); - } - }); - return this; - } - - public synchronized BtActionExecutor onFailure(Runnable callback) { - failureQ.add(new BtCallback("onFailure") { - @Override - public void onFailure() { - callback.run(); - } - }); - return this; - } - - public synchronized BtActionExecutor onFinally(Runnable callback) { - finallyQ.add(new BtCallback("finally") { - @Override - public void onFinally() { - callback.run(); - } - }); - return this; - } - - public String toString() { - StringBuilder s = new StringBuilder("Queue: "); - - Iterator it = actionQ.iterator(); - - int i = 0; - while (it.hasNext()) { - BtCallback c = it.next(); - if (i > 0) { - s.append(", "); - } - s.append(c.name); - i++; - } - - return s.toString(); - } - - public BluetoothGattCallback asCallback() { - return new MyBluetoothGattCallback(); - } - - enum EventType { - onConnectionStateChange, - onServicesDiscovered, - onCharacteristicRead, - onCharacteristicWrite, - onCharacteristicChanged, - onDescriptorRead, - onDescriptorWrite, - onReliableWriteCompleted, - - onReadRemoteRssi, - - onFailure, - onFinally, - } - - private static Boolean callCallback(EventType key, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, int newState, BtCallback btCallback) { - switch (key) { - case onConnectionStateChange: - return btCallback.onConnectionStateChange(gatt, newState); - case onServicesDiscovered: - return btCallback.onServicesDiscovered(gatt); - case onCharacteristicRead: - return btCallback.onCharacteristicRead(gatt, characteristic); - case onCharacteristicWrite: - return btCallback.onCharacteristicWrite(gatt, characteristic); - case onCharacteristicChanged: - return btCallback.onCharacteristicChanged(gatt, characteristic); - case onDescriptorRead: - return btCallback.onDescriptorRead(gatt, descriptor); - case onDescriptorWrite: - return btCallback.onDescriptorWrite(gatt, descriptor); - case onReliableWriteCompleted: - return btCallback.onReliableWriteCompleted(gatt); - - case onFailure: - btCallback.onFailure(); - return null; - case onFinally: - btCallback.onFinally(); - return null; - default: - Log.w(TAG, "Unknown callback: " + key); - return null; - } - } - - private class MyBluetoothGattCallback extends BluetoothGattCallback { - - private Queue initialActionQ = new ArrayDeque<>(actionQ); - private List events = new ArrayList<>(); - - void onEvent(EventType key, String values, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, int status, int newState) { - boolean success = status == BluetoothGatt.GATT_SUCCESS; - events.add(key + ", " + values + ", success=" + success); - - Log.i(TAG, "Operation failed: " + key + ", " + values + ", success=" + success); - - if (!success) { - doFailure(); - return; - } - - BtCallback btCallback; - synchronized (this) { - if (actionQ.isEmpty()) { - Log.d(TAG, "All Bluetooth actions are done"); - - doFinally(); - return; - } - - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // ignore - } - btCallback = actionQ.remove(); - Log.i(TAG, "Executing bt action: " + btCallback.name); - } - - try { - boolean ok = callCallback(key, gatt, characteristic, descriptor, newState, btCallback); - - if (!ok) { - Log.w(TAG, "The callback don't want to continue."); - doFinally(); - } - - if (actionQ.isEmpty()) { - Log.i(TAG, "The queue is empty"); - } - } catch (NotOverriddenException e) { - Log.w(TAG, "Unexpected callback by listener: " + key); - doFailure(); - } - } - - private void doFailure() { - StringBuilder msg = new StringBuilder(); - - msg.append("Expected events: \n"); - for (BtCallback cb : initialActionQ) { - msg.append(" ").append(cb.name).append("\n"); - } - - msg.append("Actual events: \n"); - for (String event : events) { - msg.append(" ").append(event).append("\n"); - } - - Log.w(TAG, msg.toString()); - - Log.w(TAG, "Executing " + failureQ.size() + " failure handlers"); - - for (BtCallback callback = failureQ.poll(); callback != null; callback = failureQ.poll()) { - try { - callCallback(onFailure, null, null, null, 0, callback); - } catch (NotOverriddenException e) { - return; - } - } - - doFinally(); - } - - private void doFinally() { - actionQ.clear(); - - Log.w(TAG, "Executing " + finallyQ.size() + " finally handlers"); - - for (BtCallback callback = finallyQ.poll(); callback != null; callback = finallyQ.poll()) { - try { - callCallback(onFinally, null, null, null, 0, callback); - } catch (NotOverriddenException e) { - return; - } - } - } - - // ----------------------------------------------------------------------- - // - // ----------------------------------------------------------------------- - - @Override - public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { - onEvent(onConnectionStateChange, "status=" + status + ", newState=" + newState, gatt, null, null, status, newState); - } - - @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - onEvent(onServicesDiscovered, "status=" + status, gatt, null, null, status, 9); - } - - @Override - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - onEvent(onCharacteristicRead, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0); - } - - @Override - public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - onEvent(onCharacteristicWrite, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0); - } - - @Override - public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - onEvent(onCharacteristicChanged, "characteristic=" + characteristic.getUuid(), gatt, characteristic, null, BluetoothGatt.GATT_SUCCESS, 0); - } - - @Override - public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - onEvent(onDescriptorRead, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0); - } - - @Override - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - onEvent(onDescriptorWrite, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0); - } - - @Override - public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { - onEvent(onReliableWriteCompleted, "status=" + status, gatt, null, null, status, 0); - } - - @Override - public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { -// onEvent(onReadRemoteRssi, "status=" + status, gatt, null, null, status, 0); - } - } -} diff --git a/app/src/main/java/io/trygvis/android/bt/BtCallback.java b/app/src/main/java/io/trygvis/android/bt/BtCallback.java index 29ea9e1..8dab149 100644 --- a/app/src/main/java/io/trygvis/android/bt/BtCallback.java +++ b/app/src/main/java/io/trygvis/android/bt/BtCallback.java @@ -4,6 +4,8 @@ import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; +import static io.trygvis.android.bt.BtPromise.*; + public class BtCallback { public final String name; @@ -11,39 +13,39 @@ public class BtCallback { this.name = name; } - public boolean onConnectionStateChange(BluetoothGatt gatt, int newState) { + public PromiseResult onConnectionStateChange(BluetoothGatt gatt, int newState) { throw new NotOverriddenException(); } - public boolean onServicesDiscovered(BluetoothGatt gatt) { + public PromiseResult onServicesDiscovered(BluetoothGatt gatt) { throw new NotOverriddenException(); } - public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + public PromiseResult onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { throw new NotOverriddenException(); } - public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + public PromiseResult onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { throw new NotOverriddenException(); } - public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + public PromiseResult onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { throw new NotOverriddenException(); } - public boolean onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + public PromiseResult onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { throw new NotOverriddenException(); } - public boolean onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + public PromiseResult onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { throw new NotOverriddenException(); } - public boolean onReliableWriteCompleted(BluetoothGatt gatt) { + public PromiseResult onReliableWriteCompleted(BluetoothGatt gatt) { throw new NotOverriddenException(); } - public boolean onReadRemoteRssi(BluetoothGatt gatt, int rssi) { + public PromiseResult onReadRemoteRssi(BluetoothGatt gatt, int rssi) { throw new NotOverriddenException(); } diff --git a/app/src/main/java/io/trygvis/android/bt/BtDevice.java b/app/src/main/java/io/trygvis/android/bt/BtDevice.java index edad522..1c7666e 100644 --- a/app/src/main/java/io/trygvis/android/bt/BtDevice.java +++ b/app/src/main/java/io/trygvis/android/bt/BtDevice.java @@ -1,6 +1,8 @@ package io.trygvis.android.bt; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; import android.util.Log; import java.util.Date; @@ -10,6 +12,7 @@ public class BtDevice> { private final DefaultBtService btService; private final BluetoothDevice bluetoothDevice; + private BluetoothGatt gatt; private Integer rssi; private BtScanResult scanResult; private A tag; @@ -24,8 +27,8 @@ public class BtDevice> { } BtDevice(DefaultBtService btService, BluetoothDevice bluetoothDevice, - BtService.BtDbIntegration btDbIntegration, long id, Integer rssi, - BtScanResult scanResult, boolean seenBefore, Date firstSeen, Date lastSeen) { + BtService.BtDbIntegration btDbIntegration, long id, Integer rssi, + BtScanResult scanResult, boolean seenBefore, Date firstSeen, Date lastSeen) { this.btService = btService; this.bluetoothDevice = bluetoothDevice; this.tag = btDbIntegration.createTag(this); @@ -73,12 +76,20 @@ public class BtDevice> { this.lastSeen = lastSeen; } - public boolean connect(BtActionExecutor executor) { - Log.i(TAG, "connect(), address=" + bluetoothDevice.getAddress() + ", queue=" + executor); - bluetoothDevice.connectGatt(btService, false, executor.asCallback()); + public synchronized boolean connect(BtPromise executor) { + Log.i(TAG, "connect(), address=" + bluetoothDevice.getAddress()); + BluetoothGattCallback callback = executor.asCallback(); + gatt = bluetoothDevice.connectGatt(btService, false, callback); return true; } + public synchronized void disconnect() { + if (gatt != null) { + gatt.disconnect(); + gatt = null; + } + } + public BtScanResult getScanResult() { return scanResult; } diff --git a/app/src/main/java/io/trygvis/android/bt/BtPromise.java b/app/src/main/java/io/trygvis/android/bt/BtPromise.java new file mode 100644 index 0000000..55b6315 --- /dev/null +++ b/app/src/main/java/io/trygvis/android/bt/BtPromise.java @@ -0,0 +1,404 @@ +package io.trygvis.android.bt; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.util.Log; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; + +import io.trygvis.android.F2; +import io.trygvis.android.Function; + +import static io.trygvis.android.bt.BtPromise.EventType.onCharacteristicChanged; +import static io.trygvis.android.bt.BtPromise.EventType.onCharacteristicRead; +import static io.trygvis.android.bt.BtPromise.EventType.onCharacteristicWrite; +import static io.trygvis.android.bt.BtPromise.EventType.onConnectionStateChange; +import static io.trygvis.android.bt.BtPromise.EventType.onDescriptorRead; +import static io.trygvis.android.bt.BtPromise.EventType.onDescriptorWrite; +import static io.trygvis.android.bt.BtPromise.EventType.onFailure; +import static io.trygvis.android.bt.BtPromise.EventType.onFinally; +import static io.trygvis.android.bt.BtPromise.EventType.onReliableWriteCompleted; +import static io.trygvis.android.bt.BtPromise.EventType.onServicesDiscovered; + +public class BtPromise { + private final static String TAG = BtPromise.class.getSimpleName(); + private final Queue actionQ = new ArrayDeque<>(); + private final Queue failureQ = new ArrayDeque<>(); + private final Queue finallyQ = new ArrayDeque<>(); + + public static final PromiseResult continueDownChain = new Continue(); + public static final PromiseResult doneWithChain = new Done(); + + public abstract static class PromiseResult { + } + + private static class Continue extends PromiseResult { + } + + private static class Done extends PromiseResult { + } + + private static class Detour extends PromiseResult { + final BtPromise promise; + + private Detour(BtPromise promise) { + this.promise = promise; + } + } + + public synchronized BtPromise onConnectionStateChange(F2 callback) { + actionQ.add(new BtCallback("onConnectionStateChange") { + @Override + public PromiseResult onConnectionStateChange(BluetoothGatt gatt, int newState) { + return callback.apply(gatt, newState); + } + }); + return this; + } + + public synchronized BtPromise onServicesDiscovered(Function callback) { + actionQ.add(new BtCallback("onServicesDiscovered") { + @Override + public PromiseResult onServicesDiscovered(BluetoothGatt gatt) { + return callback.apply(gatt); + } + }); + return this; + } + + public synchronized BtPromise onCharacteristicRead(F2 callback) { + actionQ.add(new BtCallback("onCharacteristicRead") { + @Override + public PromiseResult onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + return callback.apply(gatt, characteristic); + } + }); + return this; + } + + public synchronized BtPromise onCharacteristicWrite(F2 callback) { + actionQ.add(new BtCallback("onCharacteristicWrite") { + @Override + public PromiseResult onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + return callback.apply(gatt, characteristic); + } + }); + return this; + } + + public synchronized BtPromise onCharacteristicChanged(F2 callback) { + actionQ.add(new BtCallback("onCharacteristicChanged") { + @Override + public PromiseResult onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + return callback.apply(gatt, characteristic); + } + }); + return this; + } + + public synchronized BtPromise onDescriptorRead(F2 callback) { + actionQ.add(new BtCallback("onDescriptorRead") { + @Override + public PromiseResult onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + return callback.apply(gatt, descriptor); + } + }); + return this; + } + + public synchronized BtPromise onDescriptorWrite(F2 callback) { + actionQ.add(new BtCallback("onDescriptorWrite") { + @Override + public PromiseResult onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor) { + return callback.apply(gatt, descriptor); + } + }); + return this; + } + + public synchronized BtPromise onReliableWriteCompleted(Function callback) { + actionQ.add(new BtCallback("onReliableWriteCompleted") { + @Override + public PromiseResult onReliableWriteCompleted(BluetoothGatt gatt) { + return callback.apply(gatt); + } + }); + return this; + } + + public synchronized BtPromise onReadRemoteRssi(F2 callback) { + actionQ.add(new BtCallback("onReadRemoteRssi") { + @Override + public PromiseResult onReadRemoteRssi(BluetoothGatt gatt, int rssi) { + return callback.apply(gatt, rssi); + } + }); + return this; + } + + public synchronized BtPromise onFailure(Runnable callback) { + failureQ.add(new BtCallback("onFailure") { + @Override + public void onFailure() { + callback.run(); + } + }); + return this; + } + + public synchronized BtPromise onFinally(Runnable callback) { + finallyQ.add(new BtCallback("finally") { + @Override + public void onFinally() { + callback.run(); + } + }); + return this; + } + + public String toString() { + StringBuilder s = new StringBuilder("Queue: "); + + Iterator it = actionQ.iterator(); + + int i = 0; + while (it.hasNext()) { + BtCallback c = it.next(); + if (i > 0) { + s.append(", "); + } + s.append(c.name); + i++; + } + + return s.toString(); + } + + BtBluetoothGattCallback asCallback() { + return new BtBluetoothGattCallback(actionQ, failureQ, finallyQ); + } + + public PromiseResult toDetour() { + return new Detour(this); + } + + enum EventType { + onConnectionStateChange, + onServicesDiscovered, + onCharacteristicRead, + onCharacteristicWrite, + onCharacteristicChanged, + onDescriptorRead, + onDescriptorWrite, + onReliableWriteCompleted, + + onReadRemoteRssi, + + onFailure, + onFinally, + } + + private static PromiseResult callCallback(EventType key, BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, + BluetoothGattDescriptor descriptor, int newState, BtCallback btCallback) { + switch (key) { + case onConnectionStateChange: + return btCallback.onConnectionStateChange(gatt, newState); + case onServicesDiscovered: + return btCallback.onServicesDiscovered(gatt); + case onCharacteristicRead: + return btCallback.onCharacteristicRead(gatt, characteristic); + case onCharacteristicWrite: + return btCallback.onCharacteristicWrite(gatt, characteristic); + case onCharacteristicChanged: + return btCallback.onCharacteristicChanged(gatt, characteristic); + case onDescriptorRead: + return btCallback.onDescriptorRead(gatt, descriptor); + case onDescriptorWrite: + return btCallback.onDescriptorWrite(gatt, descriptor); + case onReliableWriteCompleted: + return btCallback.onReliableWriteCompleted(gatt); + + case onFailure: + btCallback.onFailure(); + return null; + case onFinally: + btCallback.onFinally(); + return null; + default: + Log.w(TAG, "Unknown callback: " + key); + return null; + } + } + + static class BtBluetoothGattCallback extends BluetoothGattCallback { + + private ArrayDeque actionQ; + private ArrayDeque failureQ; + ArrayDeque finallyQ; +// private ArrayDeque initialActionQ; + private List events = new ArrayList<>(); + + private BtBluetoothGattCallback(Queue actionQ, Queue failureQ, Queue finallyQ) { + this.actionQ = new ArrayDeque<>(actionQ); + this.failureQ = new ArrayDeque<>(failureQ); + this.finallyQ = new ArrayDeque<>(finallyQ); + } + + void onEvent(EventType key, String values, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, int status, int newState) { + boolean success = status == BluetoothGatt.GATT_SUCCESS; + events.add(key + "(" + values + "), success=" + success); + + Log.i(TAG, key + "(" + values + "), success=" + success); + + if (!success) { + doFailure(); + return; + } + + BtCallback btCallback; + synchronized (this) { + if (actionQ.isEmpty()) { + Log.d(TAG, "All Bluetooth actions are done"); + + doFinally(); + return; + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // ignore + } + btCallback = actionQ.remove(); + Log.i(TAG, "Executing bt action: " + btCallback.name); + } + + try { + PromiseResult result = callCallback(key, gatt, characteristic, descriptor, newState, btCallback); + + if (result instanceof Done) { + Log.i(TAG, "The chain is done."); + doFinally(); + } else if (result instanceof Detour) { + Log.i(TAG, "Adding detour"); + BtPromise promise = ((Detour) result).promise; + if (!promise.failureQ.isEmpty()) { + Log.i(TAG, "Ignoring " + promise.failureQ.size() + " items from the failure Q"); + } + if (!promise.finallyQ.isEmpty()) { + Log.i(TAG, "Ignoring " + promise.finallyQ.size() + " items from the finally Q"); + } + + for (BtCallback callback : promise.actionQ) { + actionQ.addFirst(callback); + } + } + + if (actionQ.isEmpty()) { + Log.i(TAG, "The queue is empty"); + } + } catch (NotOverriddenException e) { + Log.w(TAG, "Unexpected callback by listener: " + key); + doFailure(); + } + } + + private void doFailure() { + StringBuilder msg = new StringBuilder(); + + msg.append("Expected events: \n"); + for (BtCallback cb : actionQ) { + msg.append(" ").append(cb.name).append("\n"); + } + + msg.append("Actual events: \n"); + for (String event : events) { + msg.append(" ").append(event).append("\n"); + } + + Log.w(TAG, msg.toString()); + + Log.w(TAG, "Executing " + failureQ.size() + " failure handlers"); + + for (BtCallback callback = failureQ.poll(); callback != null; callback = failureQ.poll()) { + try { + callCallback(onFailure, null, null, null, 0, callback); + } catch (NotOverriddenException e) { + return; + } + } + + doFinally(); + } + + private void doFinally() { + actionQ.clear(); + + Log.w(TAG, "Executing " + finallyQ.size() + " finally handlers"); + + for (BtCallback callback = finallyQ.poll(); callback != null; callback = finallyQ.poll()) { + try { + callCallback(onFinally, null, null, null, 0, callback); + } catch (NotOverriddenException e) { + return; + } + } + } + + // ----------------------------------------------------------------------- + // + // ----------------------------------------------------------------------- + + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + onEvent(onConnectionStateChange, "status=" + status + ", newState=" + newState, gatt, null, null, status, newState); + } + + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + onEvent(onServicesDiscovered, "status=" + status, gatt, null, null, status, 9); + } + + @Override + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + onEvent(onCharacteristicRead, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0); + } + + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + onEvent(onCharacteristicWrite, "status=" + status + ", characteristic=" + characteristic.getUuid(), gatt, characteristic, null, status, 0); + } + + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + onEvent(onCharacteristicChanged, "characteristic=" + characteristic.getUuid(), gatt, characteristic, null, BluetoothGatt.GATT_SUCCESS, 0); + } + + @Override + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + onEvent(onDescriptorRead, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0); + } + + @Override + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + onEvent(onDescriptorWrite, "status=" + status + ", descriptor=" + descriptor.getUuid(), gatt, null, descriptor, status, 0); + } + + @Override + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { + onEvent(onReliableWriteCompleted, "status=" + status, gatt, null, null, status, 0); + } + + @Override + public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { +// onEvent(onReadRemoteRssi, "status=" + status, gatt, null, null, status, 0); + } + } +} diff --git a/app/src/main/java/io/trygvis/android/bt/DefaultBtService.java b/app/src/main/java/io/trygvis/android/bt/DefaultBtService.java index 205421b..80a195a 100644 --- a/app/src/main/java/io/trygvis/android/bt/DefaultBtService.java +++ b/app/src/main/java/io/trygvis/android/bt/DefaultBtService.java @@ -45,7 +45,7 @@ public class DefaultBtService> extends Ser private BtDbIntegration btDbIntegration; - private BluetoothManager bluetoothManager; + BluetoothManager bluetoothManager; private BluetoothAdapter bluetoothAdapter; diff --git a/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java b/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java index 0f08076..a2723d5 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java +++ b/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java @@ -3,6 +3,7 @@ package io.trygvis.soilmoisture; import android.app.Service; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.content.ComponentName; import android.content.ContentValues; @@ -18,13 +19,17 @@ import java.util.Set; import java.util.TreeSet; import io.trygvis.android.LocalBinder; -import io.trygvis.android.bt.BtActionExecutor; import io.trygvis.android.bt.BtDevice; +import io.trygvis.android.bt.BtPromise; import io.trygvis.android.bt.BtService; import io.trygvis.android.bt.DefaultBtService; import io.trygvis.bluetooth.TrygvisIoUuids; +import static io.trygvis.android.bt.BtPromise.continueDownChain; +import static io.trygvis.android.bt.BtPromise.doneWithChain; import static io.trygvis.android.bt.BtService.BtServiceListenerBroadcastReceiver; +import static io.trygvis.soilmoisture.SmDevice.GetSensorCountRes; +import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.GET_SENSOR_COUNT; public class DefaultSoilMoistureService extends Service implements SoilMoistureService { private final static String TAG = DefaultSoilMoistureService.class.getSimpleName(); @@ -95,37 +100,73 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS if (!smDevice.isProbed()) { Log.i(TAG, "Probing " + address + ", name=" + btDevice.getName()); - BtActionExecutor executor = new BtActionExecutor(). + BtPromise executor = new BtPromise(). onConnectionStateChange((gatt, newState) -> { //noinspection SimplifiableIfStatement if (newState == BluetoothGatt.STATE_CONNECTED) { Log.i(TAG, "Connected to " + address + ", getting services"); - return gatt.discoverServices(); - } + return gatt.discoverServices() ? continueDownChain : doneWithChain; + } else { + Log.i(TAG, "Disconnected from " + address + ", trying again"); + + return new BtPromise().onConnectionStateChange((gatt2, newState2) -> { + if (newState2 == BluetoothGatt.STATE_CONNECTED) { + Log.i(TAG, "Connected to " + address + ", getting services"); + return continueDownChain; + } - Log.i(TAG, "Could not connect to to " + address); - smDevice.setIsUseful(false); - return false; + Log.i(TAG, "Could still not connect to " + address + ", failing."); + + return doneWithChain; + }).toDetour(); + } }). onServicesDiscovered(gatt -> { Log.i(TAG, "Services discovered"); BluetoothGattService service = gatt.getService(TrygvisIoUuids.Services.SOIL_MOISTURE_SERVICE); - boolean useful = false; - if (service != null) { - BluetoothGattCharacteristic characteristic = service.getCharacteristic(TrygvisIoUuids.Characteristics.SOIL_MOISTURE); + if (service == null) { + return doneWithChain; + } - useful = characteristic != null; + BluetoothGattCharacteristic soilMoisture = service.getCharacteristic(TrygvisIoUuids.Characteristics.SOIL_MOISTURE); - smDevice.setIsUseful(useful); + if (soilMoisture == null) { + return doneWithChain; } - gatt.disconnect(); + BluetoothGattDescriptor ccg = soilMoisture.getDescriptor(TrygvisIoUuids.CLIENT_CHARACTERISTIC_CONFIG); + ccg.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + gatt.setCharacteristicNotification(soilMoisture, true); - return false; + return gatt.writeDescriptor(ccg) ? continueDownChain : doneWithChain; + }). + onDescriptorWrite((gatt, descriptor) -> { + Log.i(TAG, "Notifications enabled, getting sensor count"); + BluetoothGattCharacteristic c = descriptor.getCharacteristic(); + c.setValue(SmDevice.createGetSensorCountReq()); + return gatt.writeCharacteristic(c) ? continueDownChain : doneWithChain; + }). + onCharacteristicWrite((gatt, characteristic) -> continueDownChain). + onCharacteristicChanged((gatt, characteristic) -> { + GetSensorCountRes getSensorCountRes = SmDevice.parseResponse(characteristic.getValue(), + GET_SENSOR_COUNT, GetSensorCountRes.class); + + Log.i(TAG, "The device has " + getSensorCountRes.count + " sensors."); + + smDevice.setIsUseful(true); + smDevice.setSensorCount(getSensorCountRes.count); + + return doneWithChain; }). onFinally(() -> { + btDevice.disconnect(); + + if (smDevice.getIsUseful() == null) { + smDevice.setIsUseful(false); + } + btService.runTx(db -> { if (!btDevice.isSeenBefore() && smDevice.isUseful()) { for (SmSensor soilMonitor : smDevice.getSensors()) { diff --git a/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java b/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java index 2e6df76..7aa3534 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java +++ b/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java @@ -222,6 +222,7 @@ public class MainActivity extends ListActivity { final TextView deviceName; final TextView deviceAddress; final TextView rssi; + final TextView info; final ProgressBar spinner; final Button connect; @@ -229,6 +230,7 @@ public class MainActivity extends ListActivity { this.deviceName = (TextView) view.findViewById(R.id.device_name); this.deviceAddress = (TextView) view.findViewById(R.id.device_address); this.rssi = (TextView) view.findViewById(R.id.device_rssi); + this.info = (TextView) view.findViewById(R.id.device_info); this.spinner = (ProgressBar) view.findViewById(R.id.device_spinner); this.connect = (Button) view.findViewById(R.id.button_connect); } @@ -241,7 +243,7 @@ public class MainActivity extends ListActivity { private LayoutInflater inflater = MainActivity.this.getLayoutInflater(); private boolean groupByDevice = true; - private boolean showAll = false; + private boolean showAll = true; public void sort() { Log.i(TAG, "sort(), groupByDevice=" + groupByDevice + ", showAll=" + showAll); @@ -400,6 +402,12 @@ public class MainActivity extends ListActivity { item.rssi.setText(getText(R.string.rssi) + ": " + (smDevice.getBtDevice().getRssi() != 0 ? valueOf(smDevice.getBtDevice().getRssi()) : getText(R.string.unknown))); + if (smDevice.isUseful()) { + item.info.setText("number of sensors: " + smDevice.getSensors().size()); + } else { + item.info.setText(""); + } + boolean useful = smDevice.isUseful(); item.spinner.setVisibility(useful ? View.GONE : View.VISIBLE); item.connect.setVisibility(useful ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java b/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java index e24416c..b51b3fa 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java +++ b/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java @@ -2,11 +2,19 @@ package io.trygvis.soilmoisture; import android.util.Log; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import io.trygvis.android.bt.BtDevice; +import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.GET_SENSOR_COUNT; +import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.GET_SENSOR_NAME; +import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.GET_VALUE; +import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.GET_WARNING_VALUE; +import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.SET_SENSOR_NAME; +import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.SET_WARNING_VALUE; + class SmDevice implements BtDevice.BtDeviceWrapper { private final static String TAG = SmDevice.class.getSimpleName(); @@ -57,4 +65,109 @@ class SmDevice implements BtDevice.BtDeviceWrapper { public List getSensors() { return sensors; } + + // ----------------------------------------------------------------------- + // + // ----------------------------------------------------------------------- + + @SuppressWarnings("unchecked") + public static T parseResponse(byte[] bytes, SmCmdCode code, Class klass) { + byte c = bytes[0]; + + if (c != code.code) { + throw new RuntimeException("Expected response of type " + code + ", got " + c); + } + + Object value = null; + if (c == GET_SENSOR_COUNT.code) { + return (T) new GetSensorCountRes(bytes[1]); + } else if (c == GET_VALUE.code) { + } else if (c == SET_WARNING_VALUE.code) { + } else if (c == GET_WARNING_VALUE.code) { + } else if (c == SET_SENSOR_NAME.code) { + } else if (c == GET_SENSOR_NAME.code) { + } + + throw new RuntimeException("Unknown code: " + c); + } + + public void setSensorCount(int count) { + sensors = new ArrayList<>(); + for (int index = 0; index < count; index++) { + sensors.add(new SmSensor(this, index)); + } + } + + public static class GetSensorCountRes { + public final int count; + + public GetSensorCountRes(int count) { + this.count = count; + } + } + + public static byte[] createGetSensorCountReq() { + return new byte[]{ + GET_SENSOR_COUNT.code + }; + } + + public static byte[] createGetValueReq(byte sensor) { + return new byte[]{ + GET_VALUE.code, + sensor + }; + } + + public static byte[] createSetWarningValueReq(byte sensor, short warningValue) { + return new byte[]{ + SET_WARNING_VALUE.code, + sensor, + (byte) (warningValue & 0xff), + (byte) ((warningValue >> 8) & 0xff) + }; + } + + public static byte[] createGetWarningValueReq(byte sensor) { + return new byte[]{ + GET_WARNING_VALUE.code, + sensor + }; + } + + public static byte[] createSetSensorNameReq(byte sensor, String name) { + byte[] tmp = name.getBytes(Charset.defaultCharset()); + + byte[] bytes = new byte[3 + tmp.length]; + bytes[0] = GET_WARNING_VALUE.code; + bytes[1] = sensor; + bytes[2] = (byte) tmp.length; + System.arraycopy(tmp, 0, bytes, 1, tmp.length); + return bytes; + } + + public static byte[] createGetSensorNameReq(byte sensor) { + return new byte[]{ + GET_SENSOR_NAME.code, + sensor + }; + } + + public static final int SENSOR_NAME_LEN = 10; + + public enum SmCmdCode { + GET_SENSOR_COUNT(1), + GET_VALUE(2), + SET_WARNING_VALUE(3), + GET_WARNING_VALUE(4), + SET_SENSOR_NAME(5), + GET_SENSOR_NAME(6), + FAIL(255); + + public final byte code; + + SmCmdCode(int value) { + this.code = (byte) value; + } + } } diff --git a/app/src/main/java/io/trygvis/soilmoisture/SmSensor.java b/app/src/main/java/io/trygvis/soilmoisture/SmSensor.java index f27c364..ec4b423 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/SmSensor.java +++ b/app/src/main/java/io/trygvis/soilmoisture/SmSensor.java @@ -5,18 +5,25 @@ import java.util.Date; class SmSensor { private final SmDevice device; + private final int index; + private Date timestamp; private int lastValue; - SmSensor(SmDevice device) { + SmSensor(SmDevice device, int index) { this.device = device; + this.index = index; } public SmDevice getDevice() { return device; } + public int getIndex() { + return index; + } + public int getLastValue() { return lastValue; } diff --git a/app/src/main/res/layout/listitem_device.xml b/app/src/main/res/layout/listitem_device.xml index 0904d94..44021d9 100644 --- a/app/src/main/res/layout/listitem_device.xml +++ b/app/src/main/res/layout/listitem_device.xml @@ -31,6 +31,15 @@ android:layout_alignParentStart="true" android:layout_toStartOf="@+id/spacer"/> + +