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. --- .../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 +- 4 files changed, 185 insertions(+), 16 deletions(-) (limited to 'app/src/main/java/io/trygvis/soilmoisture') 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; } -- cgit v1.2.3