From fe238450f161a503d61c5ae59ecdd82c60c0e9ec Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Sun, 18 Jan 2015 12:52:17 +0100 Subject: BtPromise: Adding a distinction between successful and failure. o Chain can now call fail() instead of stop() to signal failure. o The finally handlers can be changed to get this info later. o Should probably make all callbacks take the BtDevice as a callback instead of BluetoothGatt and make the gatt instance available through the device. This way state can be kept in the device's tag. BtPromise: always discover services when operating inside a connection. --- app/src/main/java/io/trygvis/android/Optional.java | 45 ++++++++++++ .../java/io/trygvis/android/bt/BtCallback.java | 2 +- .../main/java/io/trygvis/android/bt/BtDevice.java | 35 ++++------ .../main/java/io/trygvis/android/bt/BtPromise.java | 80 +++++++++++++--------- .../soilmoisture/DefaultSoilMoistureService.java | 63 +++++++++++------ .../java/io/trygvis/soilmoisture/MainActivity.java | 16 +---- .../java/io/trygvis/soilmoisture/SmDevice.java | 21 ++++++ .../trygvis/soilmoisture/SoilMoistureService.java | 4 +- app/src/main/res/values/strings.xml | 1 + 9 files changed, 173 insertions(+), 94 deletions(-) create mode 100644 app/src/main/java/io/trygvis/android/Optional.java diff --git a/app/src/main/java/io/trygvis/android/Optional.java b/app/src/main/java/io/trygvis/android/Optional.java new file mode 100644 index 0000000..616f9a7 --- /dev/null +++ b/app/src/main/java/io/trygvis/android/Optional.java @@ -0,0 +1,45 @@ +package io.trygvis.android; + +public final class Optional { + private final T value; + + public Optional(T value) { + this.value = value; + } + + public T get() { + if (value == null) { + throw new IllegalStateException("get() on empty"); + } + + return value; + } + + public boolean isPresent() { + return value != null; + } + + public void ifPresent(Consumer consumer) { + if (value == null) { + return; + } + + consumer.accept(value); + } + + public static Optional of(T t) { + if (t == null) { + throw new IllegalArgumentException("t can't be null"); + } + + return new Optional<>(t); + } + + public static Optional empty() { + return new Optional<>(null); + } + + public static Optional ofNullable(T t) { + return t != null ? of(t) : empty(); + } +} 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 7ff8733..3b1fb10 100644 --- a/app/src/main/java/io/trygvis/android/bt/BtCallback.java +++ b/app/src/main/java/io/trygvis/android/bt/BtCallback.java @@ -51,7 +51,7 @@ public class BtCallback { throw new NotOverriddenException(); } - public PromiseResult onDirect(Object value) { + public PromiseResult onDirect(BluetoothGatt value) { 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 6a4d92e..23fd397 100644 --- a/app/src/main/java/io/trygvis/android/bt/BtDevice.java +++ b/app/src/main/java/io/trygvis/android/bt/BtDevice.java @@ -12,7 +12,8 @@ import java.util.Date; import static io.trygvis.android.bt.BtPromise.BtBluetoothGattCallback; import static io.trygvis.android.bt.BtPromise.PromiseResult.detour; -import static io.trygvis.android.bt.BtPromise.PromiseResult.stop; +import static io.trygvis.android.bt.BtPromise.PromiseResult.fail; +import static io.trygvis.android.bt.BtPromise.PromiseResult.waitForNextEvent; public class BtDevice { private final static String TAG = BtDevice.class.getSimpleName(); @@ -90,22 +91,10 @@ public class BtDevice { this.lastSeen = lastSeen; } -// public synchronized boolean connect(BtPromise executor) { -// Log.i(TAG, "connect(), address=" + bluetoothDevice.getAddress()); -// BluetoothGattCallback callback = executor.asCallback(bluetoothDevice.getAddress()); -// gatt = bluetoothDevice.connectGatt(btService, false, new WrappingBluetoothGattCallback(callback) { -// @Override -// public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { -// BtDevice.this.connected = newState == BluetoothGatt.STATE_CONNECTED; -// -// super.onConnectionStateChange(gatt, status, newState); -// } -// }); -// return true; -// } - /** * The first handler must handle a onDirect(). + *

+ * Services will be discovered. */ public synchronized void withConnection(BtPromise promise) { if (callback != null) { @@ -124,22 +113,26 @@ public class BtDevice { btService.sendBroadcast(btService.createDeviceConnection(address)); if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) { - Log.i(TAG, "Connected to " + address); - return detour(promise); + Log.i(TAG, "Connected to " + address + ", discovering services"); + return gatt.discoverServices() ? waitForNextEvent() : fail(); } else { Log.i(TAG, "Could not connect to " + address + ", trying again"); return detour(new BtPromise().onConnectionStateChange((gatt2, status2, newState2) -> { if (status2 == BluetoothGatt.GATT_SUCCESS && newState2 == BluetoothGatt.STATE_CONNECTED) { - Log.i(TAG, "Connected to " + address); - return detour(promise); + Log.i(TAG, "Connected to " + address + ", discovering services"); + return gatt.discoverServices() ? waitForNextEvent() : fail(); } Log.i(TAG, "Could still not connect to " + address + ", failing."); - return stop(); + return fail(); })); } + }). + onServicesDiscovered(gatt -> { + Log.i(TAG, "Services discovered, has " + gatt.getServices().size() + " services"); + return detour(promise); }); } else { newPromise = promise; @@ -157,7 +150,7 @@ public class BtDevice { } else { callback.onEvent(BtPromise.EventType.onDirect, "", gatt, null, null, BluetoothGatt.GATT_SUCCESS, - BluetoothGatt.STATE_CONNECTED, gatt); + BluetoothGatt.STATE_CONNECTED); } } diff --git a/app/src/main/java/io/trygvis/android/bt/BtPromise.java b/app/src/main/java/io/trygvis/android/bt/BtPromise.java index acbed8d..91c0359 100644 --- a/app/src/main/java/io/trygvis/android/bt/BtPromise.java +++ b/app/src/main/java/io/trygvis/android/bt/BtPromise.java @@ -33,6 +33,8 @@ public class BtPromise { private static final PromiseResult waitForNextEvent = new WaitForNextEvent(); private static final PromiseResult stop = new Stop(); + private static final PromiseResult fail = new Fail(); + private static final PromiseResult continueDirectly = new ContinueDirectly(); private Boolean stopOnFailure; @@ -44,14 +46,18 @@ public class BtPromise { return waitForNextEvent; } - public static PromiseResult continueDirectly(Object value) { - return new ContinueDirectly(value); + public static PromiseResult continueDirectly() { + return continueDirectly; } public static PromiseResult stop() { return stop; } + public static PromiseResult fail() { + return fail; + } + public static PromiseResult detour(BtPromise promise) { return new Detour(promise); } @@ -61,16 +67,14 @@ public class BtPromise { } private static class ContinueDirectly extends PromiseResult { - private final Object value; - - private ContinueDirectly(Object value) { - this.value = value; - } } private static class Stop extends PromiseResult { } + private static class Fail extends PromiseResult { + } + private static class Detour extends PromiseResult { final BtPromise promise; @@ -183,10 +187,10 @@ public class BtPromise { return this; } - public synchronized BtPromise onDirect(Function callback) { + public synchronized BtPromise onDirect(Function callback) { actionQ.add(new BtCallback(stopOnFailure(), "onDirect") { @Override - public PromiseResult onDirect(Object value) { + public PromiseResult onDirect(BluetoothGatt value) { return callback.apply(value); } }); @@ -255,7 +259,7 @@ public class BtPromise { private static PromiseResult callCallback(EventType key, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, BluetoothGattDescriptor descriptor, int status, int newState, - BtCallback btCallback, Object value) { + BtCallback btCallback) { switch (key) { case onConnectionStateChange: return btCallback.onConnectionStateChange(gatt, status, newState); @@ -275,7 +279,7 @@ public class BtPromise { return btCallback.onReliableWriteCompleted(gatt); case onDirect: - return btCallback.onDirect(value); + return btCallback.onDirect(gatt); case onFailure: btCallback.onFailure(); return null; @@ -306,7 +310,7 @@ public class BtPromise { } void onEvent(EventType key, String values, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, - BluetoothGattDescriptor descriptor, int status, int newState, Object value) { + BluetoothGattDescriptor descriptor, int status, int newState) { boolean success = status == BluetoothGatt.GATT_SUCCESS; events.add(key + "(" + values + "), success=" + success); @@ -317,7 +321,7 @@ public class BtPromise { if (!hasNext()) { Log.d(TAG, "All Bluetooth actions are done, no handler for last event."); - doFinally(); + doFinally(true); return; } @@ -326,7 +330,7 @@ public class BtPromise { if (!success) { if (btCallback.stopOnFailure) { // doFailure(); - doFinally(); + doFinally(false); return; } else { Log.i(TAG, "Last status was a failure, but the callback still want it."); @@ -343,12 +347,18 @@ public class BtPromise { try { Log.i(TAG, "Executing bt action: " + btCallback.name); - PromiseResult result = callCallback(key, gatt, characteristic, descriptor, status, newState, btCallback, - value); + PromiseResult result = callCallback(key, gatt, characteristic, descriptor, status, newState, btCallback + ); if (result instanceof Stop) { Log.i(TAG, "The chain want to stop."); - doFinally(); + doFinally(true); + return; + } + + if (result instanceof Fail) { + Log.i(TAG, "The chain returned fail()."); + doFinally(false); return; } @@ -370,20 +380,18 @@ public class BtPromise { // current stack can continue. actionQ.addAll(currentAction, detour.actionQ); -// failureQ.addAll(detour.failureQ); +// failureQ.addAll(detour.failureQ);B finallyQ.addAll(detour.finallyQ); Log.i(TAG, "hasNext(): " + hasNext()); if (hasNext()) { Log.i(TAG, "next action: " + actionQ.get(currentAction).name); } - result = PromiseResult.continueDirectly(gatt); + result = PromiseResult.continueDirectly(); } if (result instanceof ContinueDirectly) { - value = ((ContinueDirectly) result).value; - onEvent(onDirect, "value=" + value, null, null, null, BluetoothGatt.GATT_SUCCESS, 0, - value); + onEvent(onDirect, "direct", gatt, null, null, BluetoothGatt.GATT_SUCCESS, 0); return; } @@ -394,7 +402,11 @@ public class BtPromise { } catch (NotOverriddenException e) { Log.w(TAG, "Unexpected callback by listener: " + key); // doFailure(); - doFinally(); + doFinally(false); + } catch (Exception e) { + Log.w(TAG, "Exception in callback", e); +// doFailure(); + doFinally(false); } } @@ -442,16 +454,16 @@ public class BtPromise { Log.w(TAG, msg.toString()); } - private void doFinally() { + private void doFinally(boolean success) { showEvents(); actionQ.clear(); - Log.w(TAG, "Executing " + finallyQ.size() + " finally handlers"); + Log.w(TAG, "Executing " + finallyQ.size() + " finally handlers, success=" + success); for (BtCallback callback : finallyQ) { try { - callCallback(onFinally, null, null, null, 0, 0, callback, null); + callCallback(onFinally, null, null, null, 0, 0, callback); } catch (NotOverriddenException e) { return; } @@ -464,42 +476,42 @@ public class BtPromise { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { - onEvent(onConnectionStateChange, "status=" + status + ", newState=" + newState, gatt, null, null, status, newState, null); + 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, null); + 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, null); + 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, null); + 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, null); + 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, null); + 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, null); + 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, null); + onEvent(onReliableWriteCompleted, "status=" + status, gatt, null, null, status, 0); } @Override diff --git a/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java b/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java index 992bf97..25dac5c 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java +++ b/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java @@ -1,7 +1,6 @@ package io.trygvis.soilmoisture; import android.app.Service; -import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; @@ -13,6 +12,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.IBinder; import android.util.Log; +import android.widget.Toast; import java.util.ArrayList; import java.util.Comparator; @@ -133,14 +133,7 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS Log.i(TAG, "Probing " + address + ", name=" + btDevice.getName()); BtPromise executor = new BtPromise(). - onDirect(v -> { - Log.i(TAG, "Discovering services"); - BluetoothGatt gatt = (BluetoothGatt) v; - return gatt.discoverServices() ? waitForNextEvent() : stop(); - }). - onServicesDiscovered(gatt -> { - Log.i(TAG, "Services discovered"); - + onDirect(gatt -> { BluetoothGattService service = gatt.getService(TrygvisIoUuids.Services.SOIL_MOISTURE_SERVICE); if (service == null) { @@ -270,19 +263,44 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS // ----------------------------------------------------------------------- private SmDevice createTag(SQLiteDatabase db, BtDevice btDevice) { - Cursor cursor = db.query(Tables.T_SM_DEVICE, new String[]{Tables.C_ID}, Tables.C_BT_DEVICE + "=?", - new String[]{valueOf(btDevice.getId())}, null, null, null); + Cursor cursor = null; + try { + cursor = db.query(Tables.T_SM_DEVICE, new String[]{Tables.C_ID}, Tables.C_BT_DEVICE + "=?", + new String[]{valueOf(btDevice.getId())}, null, null, null); + + long id; + if (cursor.moveToNext()) { + id = cursor.getLong(0); + } else { + ContentValues values = new ContentValues(); + values.put(Tables.C_BT_DEVICE, btDevice.getId()); + id = db.insert(Tables.T_SM_DEVICE, null, values); + } - long id; - if (cursor.moveToNext()) { - id = cursor.getLong(0); - } else { - ContentValues values = new ContentValues(); - values.put(Tables.C_BT_DEVICE, btDevice.getId()); - id = db.insert(Tables.T_SM_DEVICE, null, values); - } + cursor.close(); + + String[] sensorColumns = { + Tables.C_ID, + Tables.C_INDEX, + }; + cursor = db.query(Tables.T_SM_SENSOR, sensorColumns, Tables.C_SM_DEVICE + "=?", + new String[]{valueOf(id)}, null, null, Tables.C_INDEX); - return new SmDevice(this, btDevice, id); + SmDevice device = new SmDevice(this, btDevice, id); + + if (cursor.moveToNext()) { + device.setIsUseful(true); + do { + device.addSensor(new SmSensor(device, cursor.getLong(0), cursor.getInt(1))); + } while (cursor.moveToNext()); + } + + return device; + } finally { + if (cursor != null) { + cursor.close(); + } + } } void handleNewSensorValueReady(SmSensor sensor, int value) { @@ -302,8 +320,7 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS void readCurrentValue(SmSensor sensor) { BtPromise promise = new BtPromise(). - onDirect(v -> { - BluetoothGatt gatt = (BluetoothGatt) v; + onDirect(gatt -> { BluetoothGattService service = gatt.getService(TrygvisIoUuids.Services.SOIL_MOISTURE_SERVICE); BluetoothGattCharacteristic soilMoisture = service.getCharacteristic(TrygvisIoUuids.Characteristics.SOIL_MOISTURE); @@ -328,6 +345,8 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS return stop(); }). onFinally(() -> { + Toast.makeText(context, R.string.error_could_not_read_value, Toast.LENGTH_SHORT). + show(); }); sensor.getDevice().getBtDevice().withConnection(promise); diff --git a/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java b/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java index e9ecae2..c6293c6 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java +++ b/app/src/main/java/io/trygvis/soilmoisture/MainActivity.java @@ -219,12 +219,6 @@ public class MainActivity extends ListActivity { public static class BtDeviceDialogFragment extends DialogFragment { private MainActivity mainActivity; -// private final BtDevice device; -// -// public BtDeviceDialogFragment(MainActivity mainActivity, BtDevice device) { -// this.mainActivity = mainActivity; -// this.device = device; -// } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { @@ -472,7 +466,7 @@ public class MainActivity extends ListActivity { @Override public View getView(int position, View view, ViewGroup viewGroup) { - Log.i(TAG, "getView, position=" + position + ", view=" + view); +// Log.i(TAG, "getView, position=" + position + ", view=" + view); Object o = current.get(position); if (o instanceof BtDevice) { @@ -491,8 +485,6 @@ public class MainActivity extends ListActivity { if (view == null) { view = inflater.inflate(R.layout.fragment_device, null); view.setTag(new DeviceItem(view)); - view.setClickable(false); - view.setLongClickable(true); view.setOnLongClickListener(v -> MainActivity.this.onBtDeviceLongClick(device)); } @@ -584,11 +576,7 @@ public class MainActivity extends ListActivity { text += ", value: " + (value == null ? "Unknown" : value); item.description.setText(text); - if (value != null) { - item.sensorProgress.setProgress(value); - } else { - item.sensorProgress.setIndeterminate(true); - } + item.sensorProgress.setProgress(value != null ? value : 0); return view; } diff --git a/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java b/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java index 1074dd9..d56f33e 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java +++ b/app/src/main/java/io/trygvis/soilmoisture/SmDevice.java @@ -6,8 +6,11 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; +import io.trygvis.android.Optional; import io.trygvis.android.bt.BtDevice; +import static io.trygvis.android.Optional.empty; +import static io.trygvis.android.Optional.of; 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; @@ -75,7 +78,25 @@ class SmDevice { return sensors; } + public Optional getSensorByIndex(int index) { + if (!isUseful()) { + throw new IllegalStateException("Not a useful device"); + } + + for (SmSensor sensor : sensors) { + if (sensor.getIndex() == index) { + return of(sensor); + } + } + + return empty(); + } + void addSensor(SmSensor sensor) { + if (getSensorByIndex(sensor.index).isPresent()) { + throw new IllegalStateException("This device already contains a sensor with index=" + sensor.index); + } + sensors.add(sensor); } diff --git a/app/src/main/java/io/trygvis/soilmoisture/SoilMoistureService.java b/app/src/main/java/io/trygvis/soilmoisture/SoilMoistureService.java index a287c7b..5e3ddbb 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/SoilMoistureService.java +++ b/app/src/main/java/io/trygvis/soilmoisture/SoilMoistureService.java @@ -41,10 +41,10 @@ public interface SoilMoistureService { public void onScanStarted() { } - public void onNewDevice(String address) { + public void onScanStopped() { } - public void onScanStopped() { + public void onNewDevice(String address) { } public void onNewSample(String address, int sensor) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2dc3820..26f42fe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,5 +36,6 @@ Probe device? Yes No + Could not read value -- cgit v1.2.3