From 17a1f7227c8c3872fce7bbcc2f5cd46540f9ac52 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Sun, 11 Jan 2015 12:28:55 +0100 Subject: o Reading values from the soil sensor. o Rewrote the database schema to match the new device+sensors model. o Storing samples in the database. o To be able to reuse BT callbacks, added a way to always to directly to the next step instead of waiting for an event. --- .../soilmoisture/DefaultSoilMoistureService.java | 231 ++++++++++++++++----- 1 file changed, 178 insertions(+), 53 deletions(-) (limited to 'app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java') diff --git a/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java b/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java index a2723d5..3ff6e66 100644 --- a/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java +++ b/app/src/main/java/io/trygvis/soilmoisture/DefaultSoilMoistureService.java @@ -9,12 +9,17 @@ import android.content.ComponentName; import android.content.ContentValues; import android.content.Intent; import android.content.ServiceConnection; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; import android.os.IBinder; import android.util.Log; import java.util.ArrayList; import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeSet; @@ -25,15 +30,24 @@ 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.BtPromise.PromiseResult.continueDirectly; +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.waitForNextEvent; import static io.trygvis.android.bt.BtService.BtServiceListenerBroadcastReceiver; +import static io.trygvis.bluetooth.TrygvisIoUuids.CLIENT_CHARACTERISTIC_CONFIG; import static io.trygvis.soilmoisture.SmDevice.GetSensorCountRes; +import static io.trygvis.soilmoisture.SmDevice.GetValueRes; import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.GET_SENSOR_COUNT; +import static io.trygvis.soilmoisture.SmDevice.SmCmdCode.GET_VALUE; +import static io.trygvis.soilmoisture.SmDevice.createGetSensorCountReq; +import static io.trygvis.soilmoisture.SmDevice.createGetValueReq; +import static io.trygvis.soilmoisture.SmDevice.parseResponse; +import static java.lang.String.valueOf; +import static java.lang.System.currentTimeMillis; public class DefaultSoilMoistureService extends Service implements SoilMoistureService { private final static String TAG = DefaultSoilMoistureService.class.getSimpleName(); - private final static int DEFAULT_WARNING_LEVEL = 750; private final IBinder binder = new LocalBinder<>(this); @@ -58,7 +72,7 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS @Override public void onServiceConnected(ComponentName componentName, IBinder service) { btService = ((LocalBinder>) service).getService(); - boolean ok = btService.initialize(DefaultSoilMoistureService.this::onNewDevice); + boolean ok = btService.initialize(DefaultSoilMoistureService.this::createTag); sendBroadcast(createReady(ok)); } @@ -98,28 +112,16 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS BtDevice btDevice = btService.getDevice(address); SmDevice smDevice = btDevice.getTag(); + sendBroadcast(createNewDevice(address)); + if (!smDevice.isProbed()) { Log.i(TAG, "Probing " + address + ", name=" + btDevice.getName()); 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() ? 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 still not connect to " + address + ", failing."); - - return doneWithChain; - }).toDetour(); - } + ignoreFailureForNext(). + onConnectionStateChange(DefaultSoilMoistureService::defaultConnectCallback). + onDirect(v -> { + BluetoothGatt gatt = (BluetoothGatt) v; + return gatt.discoverServices() ? waitForNextEvent() : stop(); }). onServicesDiscovered(gatt -> { Log.i(TAG, "Services discovered"); @@ -127,38 +129,37 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS BluetoothGattService service = gatt.getService(TrygvisIoUuids.Services.SOIL_MOISTURE_SERVICE); if (service == null) { - return doneWithChain; + return stop(); } BluetoothGattCharacteristic soilMoisture = service.getCharacteristic(TrygvisIoUuids.Characteristics.SOIL_MOISTURE); if (soilMoisture == null) { - return doneWithChain; + return stop(); } BluetoothGattDescriptor ccg = soilMoisture.getDescriptor(TrygvisIoUuids.CLIENT_CHARACTERISTIC_CONFIG); ccg.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); gatt.setCharacteristicNotification(soilMoisture, true); - return gatt.writeDescriptor(ccg) ? continueDownChain : doneWithChain; + return gatt.writeDescriptor(ccg) ? waitForNextEvent() : stop(); }). 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; + c.setValue(createGetSensorCountReq()); + return gatt.writeCharacteristic(c) ? waitForNextEvent() : stop(); }). - onCharacteristicWrite((gatt, characteristic) -> continueDownChain). + onCharacteristicWrite((gatt, characteristic) -> waitForNextEvent()). onCharacteristicChanged((gatt, characteristic) -> { - GetSensorCountRes getSensorCountRes = SmDevice.parseResponse(characteristic.getValue(), + GetSensorCountRes getSensorCountRes = parseResponse(characteristic.getValue(), GET_SENSOR_COUNT, GetSensorCountRes.class); Log.i(TAG, "The device has " + getSensorCountRes.count + " sensors."); - smDevice.setIsUseful(true); - smDevice.setSensorCount(getSensorCountRes.count); + markDeviceAsUseful(smDevice, getSensorCountRes.count); - return doneWithChain; + return stop(); }). onFinally(() -> { btDevice.disconnect(); @@ -167,28 +168,69 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS smDevice.setIsUseful(false); } - btService.runTx(db -> { - if (!btDevice.isSeenBefore() && smDevice.isUseful()) { - for (SmSensor soilMonitor : smDevice.getSensors()) { - ContentValues values = new ContentValues(); - values.put("bt_device", btDevice.getId()); - values.put("warning_level", DEFAULT_WARNING_LEVEL); - db.insert("soil_monitor", null, values); - } - } - - return null; - }); - sendBroadcast(createNewDevice(address)); }); btDevice.connect(executor); - } else { - sendBroadcast(createNewDevice(address)); } } }; + private void markDeviceAsUseful(SmDevice device, int sensorCount) { + btService.runTx(db -> { + device.setIsUseful(true); + + // Find all already registered sensors and register the rest + Map indexes = new HashMap<>(); + Cursor cursor = db.query(Tables.T_SM_SENSOR, new String[]{Tables.C_ID, Tables.C_INDEX}, + Tables.C_SM_DEVICE + "=?", new String[]{valueOf(device.id)}, null, null, null); + while (cursor.moveToNext()) { + indexes.put(cursor.getInt(1), cursor.getLong(0)); + } + + ContentValues values = new ContentValues(); + values.put(Tables.C_USEFUL, true); + db.update(Tables.T_SM_DEVICE, values, "id=?", new String[]{valueOf(device.id)}); + + for (int i = 0; i < sensorCount; i++) { + Long id = indexes.get(i); + + if (id == null) { + values = new ContentValues(); + values.put(Tables.C_SM_DEVICE, device.id); + values.put(Tables.C_INDEX, i); + id = db.insert(Tables.T_SM_SENSOR, null, values); + } + + device.addSensor(new SmSensor(device, id, (byte) i)); + } + + return null; + }); + } + + public static BtPromise.PromiseResult defaultConnectCallback(BluetoothGatt gatt, int status, Integer newState) { + Log.i(TAG, "defaultConnectCallback: status=" + status + ", newState=" + newState); + String address = gatt.getDevice().getAddress(); + //noinspection SimplifiableIfStatement + if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) { + Log.i(TAG, "Connected to " + address); + return continueDirectly(gatt); + } else { + Log.i(TAG, "Disconnected from " + address + ", trying again"); + + return detour(new BtPromise().onConnectionStateChange((gatt2, status2, newState2) -> { + if (newState2 == BluetoothGatt.STATE_CONNECTED) { + Log.i(TAG, "Connected to " + address); + return continueDirectly(gatt); + } + + Log.i(TAG, "Could still not connect to " + address + ", failing."); + + return stop(); + })); + } + } + // ----------------------------------------------------------------------- // SmDevicesManager Implementation // ----------------------------------------------------------------------- @@ -223,13 +265,82 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS } // ----------------------------------------------------------------------- - // Event creation and dispatching + // // ----------------------------------------------------------------------- - private SmDevice onNewDevice(BtDevice btDevice) { - return new SmDevice(btDevice); + 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); + + 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); + } + + return new SmDevice(this, btDevice, id); + } + + void handleNewSensorValueReady(SmSensor sensor, int value) { + long timestamp = currentTimeMillis(); + btService.runTx(db -> { + ContentValues values = new ContentValues(); + values.put(Tables.C_SM_SENSOR, sensor.getId()); + values.put(Tables.C_TIMESTAMP, timestamp); + values.put(Tables.C_VALUE, value); + return db.insert(Tables.T_SOIL_SAMPLE, null, values); + }); + + sensor.updateLastValue(new Date(timestamp), value); + sendBroadcast(createNewSample(sensor)); + } + + void readCurrentValue(SmSensor sensor) { + BtPromise promise = new BtPromise(). + onConnectionStateChange(DefaultSoilMoistureService::defaultConnectCallback). + onDirect(v -> { + BluetoothGatt gatt = (BluetoothGatt) v; + return gatt.discoverServices() ? waitForNextEvent() : stop(); + }). + onServicesDiscovered(gatt -> { + BluetoothGattService service = gatt.getService(TrygvisIoUuids.Services.SOIL_MOISTURE_SERVICE); + BluetoothGattCharacteristic soilMoisture = service.getCharacteristic(TrygvisIoUuids.Characteristics.SOIL_MOISTURE); + + BluetoothGattDescriptor ccg = soilMoisture.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG); + ccg.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + gatt.setCharacteristicNotification(soilMoisture, true); + + return gatt.writeDescriptor(ccg) ? waitForNextEvent() : stop(); + }). + onDescriptorWrite((gatt, descriptor) -> { + BluetoothGattCharacteristic c = descriptor.getCharacteristic(); + c.setValue(createGetValueReq((byte) sensor.getIndex())); + return gatt.writeCharacteristic(c) ? waitForNextEvent() : stop(); + }). + onCharacteristicWrite((gatt, characteristic) -> waitForNextEvent()). + onCharacteristicChanged((gatt, characteristic) -> { + GetValueRes getSensorCountRes = parseResponse(characteristic.getValue(), + GET_VALUE, GetValueRes.class); + + handleNewSensorValueReady(sensor, getSensorCountRes.value); + + gatt.disconnect(); + + return stop(); + }). + onFinally(() -> { + }); + + sensor.getDevice().getBtDevice().connect(promise); } + // ----------------------------------------------------------------------- + // Event creation and dispatching + // ----------------------------------------------------------------------- + private Intent createReady(boolean success) { return new Intent(SoilMoistureListener.INTENT_NAME). putExtra("event", "ready"). @@ -252,15 +363,24 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS putExtra("address", address); } + private Intent createNewSample(SmSensor sensor) { + return new Intent(SoilMoistureListener.INTENT_NAME). + putExtra("event", "newSample"). + putExtra("address", sensor.getDevice().getBtDevice().getAddress()). + putExtra("index", sensor.getIndex()); + } + public static void dispatchEvent(Intent intent, SoilMoistureListener listener) { String event = intent.getStringExtra("event"); Log.i(TAG, "Dispatching event " + intent.getAction() + "/" + event); switch (event) { case "ready": - listener.onReady(intent.getBooleanExtra("success", false)); + listener.onReady( + intent.getBooleanExtra("success", false)); break; case "newDevice": - listener.onNewDevice(intent.getStringExtra("address")); + listener.onNewDevice( + intent.getStringExtra("address")); break; case "scanStarted": listener.onScanStarted(); @@ -268,6 +388,11 @@ public class DefaultSoilMoistureService extends Service implements SoilMoistureS case "scanStopped": listener.onScanStopped(); break; + case "newSample": + listener.onNewSample( + intent.getStringExtra("address"), + intent.getIntExtra("index", -1)); + break; default: break; } -- cgit v1.2.3