From b0a078fe4f56dfa125c03e14bce642e38c049fb2 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Fri, 13 Mar 2015 17:04:36 +0100 Subject: [PATCH] Version 1.12 BleManagers refactoring, support for keeping bond information in DFU (DFU v0.6), support for Distribution packets in DFU. --- .idea/modules.xml | 2 + app/app.iml | 1 + app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 4 +- .../android/nrftoolbox/FeaturesActivity.java | 7 +- .../nrftoolbox/adapter/AppAdapter.java | 11 +- .../app/ExpandableListActivity.java | 9 +- .../android/nrftoolbox/bpm/BPMActivity.java | 20 +- .../android/nrftoolbox/bpm/BPMManager.java | 267 ++---- .../nrftoolbox/bpm/BPMManagerCallbacks.java | 10 - .../android/nrftoolbox/csc/CSCActivity.java | 15 +- .../android/nrftoolbox/csc/CSCManager.java | 273 +----- .../android/nrftoolbox/csc/CSCService.java | 42 +- .../csc/settings/SettingsFragment.java | 3 +- .../android/nrftoolbox/dfu/DfuActivity.java | 60 +- .../nrftoolbox/dfu/DfuInitiatorActivity.java | 5 +- .../android/nrftoolbox/dfu/DfuService.java | 3 +- .../nrftoolbox/dfu/NotificationActivity.java | 3 +- .../dfu/adapter/FileBrowserAppsAdapter.java | 3 +- .../dfu/fragment/UploadCancelFragment.java | 5 +- .../dfu/fragment/ZipInfoFragment.java | 3 +- .../dfu/settings/AboutDfuPreference.java | 5 +- .../dfu/settings/SettingsFragment.java | 6 +- .../gls/ExpandableRecordAdapter.java | 3 +- .../nrftoolbox/gls/GlucoseActivity.java | 45 +- .../nrftoolbox/gls/GlucoseManager.java | 614 +++++-------- .../gls/GlucoseManagerCallbacks.java | 6 - .../android/nrftoolbox/hrs/HRSActivity.java | 52 +- .../android/nrftoolbox/hrs/HRSManager.java | 225 ++--- .../nrftoolbox/hrs/HRSManagerCallbacks.java | 5 - .../android/nrftoolbox/hrs/LineGraphView.java | 10 +- .../android/nrftoolbox/hts/HTSActivity.java | 23 +- .../android/nrftoolbox/hts/HTSManager.java | 232 +---- .../android/nrftoolbox/hts/HTSService.java | 51 +- .../nrftoolbox/parser/AlertLevelParser.java | 47 + .../BloodPressureMeasurementParser.java | 99 +++ .../parser/BodySensorLocationParser.java | 56 ++ .../parser/CSCMeasurementParser.java | 70 ++ .../nrftoolbox/parser/DateTimeParser.java | 61 ++ .../GlucoseMeasurementContextParser.java | 187 ++++ .../parser/GlucoseMeasurementParser.java | 164 ++++ .../parser/HeartRateMeasurementParser.java | 109 +++ .../IntermediateCuffPressureParser.java | 95 ++ .../parser/RSCMeasurementParser.java | 71 ++ .../RecordAccessControlPointParser.java | 171 ++++ .../parser/TemperatureMeasurementParser.java | 82 ++ .../parser/TemperatureTypeParser.java | 58 ++ .../nrftoolbox/profile/BleManager.java | 838 +++++++++++++++++- .../profile/BleManagerCallbacks.java | 10 + .../profile/BleProfileActivity.java | 60 +- .../BleProfileExpandableListActivity.java | 59 +- .../nrftoolbox/profile/BleProfileService.java | 108 ++- .../BleProfileServiceReadyActivity.java | 142 +-- .../proximity/LinklossFragment.java | 3 +- .../proximity/ProximityActivity.java | 61 +- .../proximity/ProximityManager.java | 450 +++------- .../proximity/ProximityManagerCallbacks.java | 4 +- .../proximity/ProximityService.java | 198 ++++- .../android/nrftoolbox/rsc/RSCActivity.java | 15 +- .../android/nrftoolbox/rsc/RSCManager.java | 217 +---- .../android/nrftoolbox/rsc/RSCService.java | 36 +- .../rsc/settings/SettingsFragment.java | 2 - .../nrftoolbox/scanner/DeviceListAdapter.java | 7 +- .../nrftoolbox/scanner/ScannerFragment.java | 11 +- .../scanner/ScannerServiceParser.java | 6 +- .../android/nrftoolbox/uart/UARTActivity.java | 12 +- .../nrftoolbox/uart/UARTButtonAdapter.java | 3 +- .../nrftoolbox/uart/UARTControlFragment.java | 3 +- .../nrftoolbox/uart/UARTEditDialog.java | 3 +- .../uart/UARTLocalLogContentProvider.java | 3 +- .../nrftoolbox/uart/UARTLogAdapter.java | 9 +- .../nrftoolbox/uart/UARTLogFragment.java | 9 +- .../android/nrftoolbox/uart/UARTManager.java | 155 +--- .../android/nrftoolbox/uart/UARTService.java | 40 +- .../nrftoolbox/utility/DebugLogger.java | 20 +- .../nrftoolbox/utility/ParserUtils.java | 54 ++ .../widget/TrebuchetBoldTextView.java | 3 +- .../nrftoolbox/widget/TrebuchetTextView.java | 3 +- app/src/main/res/menu/csc_menu.xml | 37 - app/src/main/res/menu/dfu_menu.xml | 37 - .../res/raw/ble_app_hrs_dfu_s110_v8_0_0.zip | Bin 0 -> 20630 bytes .../raw/{dfu_mac_3_0.sh => dfu_mac_3_1.sh} | 6 +- .../raw/{dfu_win_3_0.bat => dfu_win_3_1.bat} | 4 + app/src/main/res/values/strings_dfu.xml | 41 +- app/src/main/res/values/strings_proximity.xml | 4 +- app/src/main/res/xml/settings_dfu.xml | 4 + nRFToolbox.iml | 6 +- 87 files changed, 3576 insertions(+), 2404 deletions(-) create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/AlertLevelParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BloodPressureMeasurementParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BodySensorLocationParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/CSCMeasurementParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/DateTimeParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/GlucoseMeasurementContextParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/GlucoseMeasurementParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/HeartRateMeasurementParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/IntermediateCuffPressureParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/RSCMeasurementParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/RecordAccessControlPointParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/TemperatureMeasurementParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/TemperatureTypeParser.java create mode 100644 app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/ParserUtils.java delete mode 100644 app/src/main/res/menu/csc_menu.xml delete mode 100644 app/src/main/res/menu/dfu_menu.xml create mode 100644 app/src/main/res/raw/ble_app_hrs_dfu_s110_v8_0_0.zip rename app/src/main/res/raw/{dfu_mac_3_0.sh => dfu_mac_3_1.sh} (92%) rename app/src/main/res/raw/{dfu_win_3_0.bat => dfu_win_3_1.bat} (97%) diff --git a/.idea/modules.xml b/.idea/modules.xml index 276651450..0b9e7f959 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,6 +2,8 @@ + + diff --git a/app/app.iml b/app/app.iml index bbc7e2985..7f3aabb1d 100644 --- a/app/app.iml +++ b/app/app.iml @@ -85,6 +85,7 @@ + diff --git a/app/build.gradle b/app/build.gradle index f46855385..b9e5b9abd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "no.nordicsemi.android.nrftoolbox" minSdkVersion 18 targetSdkVersion 21 - versionCode 28 - versionName "1.11.5" + versionCode 29 + versionName "1.12.0" } buildTypes { release { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b7142251a..1224b5852 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,8 +23,8 @@ + android:versionCode="29" + android:versionName="1.12.0" > initializeManager() { - final BPMManager manager = BPMManager.getBPMManager(); + final BPMManager manager = BPMManager.getBPMManager(getApplicationContext()); manager.setGattCallbacks(this); return manager; } @@ -100,12 +107,7 @@ public void onServicesDiscovered(final boolean optionalServicesFound) { } @Override - public void onBloodPressureMeasurementIndicationsEnabled() { - // this may notify user - } - - @Override - public void onIntermediateCuffPressureNotificationEnabled() { + public void onDeviceReady() { // this may notify user } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManager.java index 717ffca13..152ae9734 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManager.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManager.java @@ -21,181 +21,107 @@ */ package no.nordicsemi.android.nrftoolbox.bpm; -import java.util.Calendar; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; -import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -public class BPMManager implements BleManager { - private final String TAG = "BPMManager"; +import java.util.Calendar; +import java.util.LinkedList; +import java.util.Queue; +import java.util.UUID; - private BPMManagerCallbacks mCallbacks; - private BluetoothGatt mBluetoothGatt; - private Context mContext; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.parser.BloodPressureMeasurementParser; +import no.nordicsemi.android.nrftoolbox.parser.IntermediateCuffPressureParser; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +public class BPMManager extends BleManager { + /** Blood Pressure service UUID */ public final static UUID BP_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb"); - public final static UUID BATTERY_SERVICE = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb"); - /** Blood Pressure Measurement characteristic */ + /** Blood Pressure Measurement characteristic UUID */ private static final UUID BPM_CHARACTERISTIC_UUID = UUID.fromString("00002A35-0000-1000-8000-00805f9b34fb"); - /** Intermediate Cuff Pressure characteristic */ + /** Intermediate Cuff Pressure characteristic UUID */ private static final UUID ICP_CHARACTERISTIC_UUID = UUID.fromString("00002A36-0000-1000-8000-00805f9b34fb"); - /** Battery Level characteristic */ - private final static UUID BATTERY_LEVEL_CHARACTERISTIC = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb"); - /** Client configuration descriptor that will allow us to enable notifications and indications */ - private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); - - private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change"; - private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services"; - private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor"; - private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information"; - private BluetoothGattCharacteristic mBPMCharacteristic, mICPCharacteristic, mBatteryCharacteristic; + private BluetoothGattCharacteristic mBPMCharacteristic, mICPCharacteristic; private static BPMManager managerInstance = null; /** * Returns the singleton implementation of BPMManager */ - public static synchronized BPMManager getBPMManager() { + public static synchronized BPMManager getBPMManager(final Context context) { if (managerInstance == null) { - managerInstance = new BPMManager(); + managerInstance = new BPMManager(context); } return managerInstance; } - /** - * Callbacks for activity {@link BPMActivity} that implements {@link BPMManagerCallbacks} interface activity use this method to register itself for receiving callbacks - */ - @Override - public void setGattCallbacks(final BPMManagerCallbacks callbacks) { - mCallbacks = callbacks; + private BPMManager(final Context context) { + super(context); } @Override - public void connect(final Context context, final BluetoothDevice device) { - mBluetoothGatt = device.connectGatt(context, false, mGattCallback); - mContext = context; + protected BleManagerGattCallback getGattCallback() { + return mGattCallback; } - @Override - public void disconnect() { - if (mBluetoothGatt != null) { - mBluetoothGatt.disconnect(); - } - } + /** + * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc + */ + private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() { - private final BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() { @Override - public void onReceive(final Context context, final Intent intent) { - final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); - final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); - - DebugLogger.d(TAG, "Bond state changed for: " + device.getAddress() + " new state: " + bondState + " previous: " + previousBondState); - - // skip other devices - if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress())) - return; - - if (bondState == BluetoothDevice.BOND_BONDED) { - // We've read Battery Level, now let'so enable ICP notifications or BPM indications - if (mICPCharacteristic != null) - enableIntermediateCuffPressureNotification(mBluetoothGatt); - else - enableBloodPressureMeasurementIndication(mBluetoothGatt); - - mContext.unregisterReceiver(this); - mCallbacks.onBonded(); - } + protected Queue initGatt(final BluetoothGatt gatt) { + final LinkedList requests = new LinkedList<>(); + if (mICPCharacteristic != null) + requests.push(Request.newEnableNotificationsRequest(mICPCharacteristic)); + requests.push(Request.newEnableIndicationsRequest(mBPMCharacteristic)); + return requests; } - }; - /** - * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc - */ - private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override - public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (newState == BluetoothProfile.STATE_CONNECTED) { - mCallbacks.onDeviceConnected(); - // start discovering services - gatt.discoverServices(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - mCallbacks.onDeviceDisconnected(); - gatt.close(); - } - } else { - mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); + protected boolean isRequiredServiceSupported(final BluetoothGatt gatt) { + BluetoothGattService service = gatt.getService(BP_SERVICE_UUID); + if (service != null) { + mBPMCharacteristic = service.getCharacteristic(BPM_CHARACTERISTIC_UUID); + mICPCharacteristic = service.getCharacteristic(ICP_CHARACTERISTIC_UUID); } + return mBPMCharacteristic != null; } @Override - public void onServicesDiscovered(final BluetoothGatt gatt, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - for (BluetoothGattService service : gatt.getServices()) { - if (BP_SERVICE_UUID.equals(service.getUuid())) { - mBPMCharacteristic = service.getCharacteristic(BPM_CHARACTERISTIC_UUID); - mICPCharacteristic = service.getCharacteristic(ICP_CHARACTERISTIC_UUID); - } else if (BATTERY_SERVICE.equals(service.getUuid())) { - mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC); - } - } - // Validate the device - if (mBPMCharacteristic == null) { - mCallbacks.onDeviceNotSupported(); - gatt.disconnect(); - return; - } - mCallbacks.onServicesDiscovered(mICPCharacteristic != null); + protected boolean isOptionalServiceSupported(final BluetoothGatt gatt) { + return mICPCharacteristic != null; + } - // We have discovered services, let's start notifications and indications, one by one: battery, icp (if exists), bpm - if (mBatteryCharacteristic != null) { - readBatteryLevel(gatt); - } else if (mICPCharacteristic != null) { - enableIntermediateCuffPressureNotification(gatt); - } else { - enableBloodPressureMeasurementIndication(gatt); - } - } else { - DebugLogger.e(TAG, "onServicesDiscovered error " + status); - mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status); - } + @Override + protected void onDeviceDisconnected() { + mICPCharacteristic = null; + mBPMCharacteristic = null; } @Override - public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (BATTERY_LEVEL_CHARACTERISTIC.equals(characteristic.getUuid())) { - final int batteryValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0); - mCallbacks.onBatteryValueReceived(batteryValue); + protected void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + // Intermediate Cuff Pressure characteristic read + if (mLogSession != null) + Logger.a(mLogSession, IntermediateCuffPressureParser.parse(characteristic)); - // We've read Battery Level, now let'so enable ICP notifications or BPM indications - if (mICPCharacteristic != null) - enableIntermediateCuffPressureNotification(gatt); - else - enableBloodPressureMeasurementIndication(gatt); - } - } else { - mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status); - } + parseBPMValue(characteristic); } @Override - public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { - // ICP or BPM characteristic returned value + protected void onCharacteristicIndicated(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + // Blood Pressure Measurement characteristic read + if (mLogSession != null) + Logger.a(mLogSession, BloodPressureMeasurementParser.parse(characteristic)); + + parseBPMValue(characteristic); + } + + private void parseBPMValue(final BluetoothGattCharacteristic characteristic) { + // Both BPM and ICP have the same structure. // first byte - flags int offset = 0; @@ -206,14 +132,14 @@ public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGat final boolean pulseRatePresent = (flags & 0x04) > 0; if (BPM_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) { - // following bytes - systolic, diastolic and mean arterial pressure + // following bytes - systolic, diastolic and mean arterial pressure final float systolic = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); final float diastolic = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 2); final float meanArterialPressure = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 4); offset += 6; mCallbacks.onBloodPressureMeasurementRead(systolic, diastolic, meanArterialPressure, unit); } else if (ICP_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) { - // following bytes - cuff pressure. Diastolic and MAP are unused + // following bytes - cuff pressure. Diastolic and MAP are unused final float cuffPressure = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); offset += 6; mCallbacks.onIntermediateCuffPressureRead(cuffPressure, unit); @@ -223,7 +149,7 @@ public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGat if (timestampPresent) { final Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset)); - calendar.set(Calendar.MONTH, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2) + 1); // months are 1-based + calendar.set(Calendar.MONTH, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2) - 1); // months are 1-based calendar.set(Calendar.DAY_OF_MONTH, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 3)); calendar.set(Calendar.HOUR_OF_DAY, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 4)); calendar.set(Calendar.MINUTE, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 5)); @@ -236,81 +162,10 @@ public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGat // parse pulse rate if present if (pulseRatePresent) { final float pulseRate = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); - offset += 2; + // offset += 2; mCallbacks.onPulseRateRead(pulseRate); } else mCallbacks.onPulseRateRead(-1.0f); } - - @Override - public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (BPM_CHARACTERISTIC_UUID.equals(descriptor.getCharacteristic().getUuid())) - mCallbacks.onBloodPressureMeasurementIndicationsEnabled(); - - if (ICP_CHARACTERISTIC_UUID.equals(descriptor.getCharacteristic().getUuid())) { - mCallbacks.onIntermediateCuffPressureNotificationEnabled(); - enableBloodPressureMeasurementIndication(gatt); - } - } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { - if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE) { - mCallbacks.onBondingRequired(); - - final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - mContext.registerReceiver(mBondingBroadcastReceiver, filter); - } else { - DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); - mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); - } - } else { - mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status); - } - } - - /** - * Reads battery level on the device - */ - private void readBatteryLevel(final BluetoothGatt gatt) { - DebugLogger.d(TAG, "readBatteryLevel()"); - gatt.readCharacteristic(mBatteryCharacteristic); - } }; - - /** - * Enabling notification on Intermediate Cuff Pressure Characteristic - */ - private void enableIntermediateCuffPressureNotification(final BluetoothGatt gatt) { - DebugLogger.d(TAG, "enableIntermediateCuffPressureNotification()"); - gatt.setCharacteristicNotification(mICPCharacteristic, true); - final BluetoothGattDescriptor descriptor = mICPCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(descriptor); - } - - /** - * Enabling indications on Blood Pressure Measurement Characteristic - */ - private void enableBloodPressureMeasurementIndication(final BluetoothGatt gatt) { - DebugLogger.d(TAG, "enableBloodPressureMeasurementIndication()"); - gatt.setCharacteristicNotification(mBPMCharacteristic, true); - final BluetoothGattDescriptor descriptor = mBPMCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); - gatt.writeDescriptor(descriptor); - } - - @Override - public void closeBluetoothGatt() { - try { - mContext.unregisterReceiver(mBondingBroadcastReceiver); - } catch (Exception e) { - // the receiver must have been not registered or unregistered before - } - - if (mBluetoothGatt != null) { - mBluetoothGatt.close(); - mBPMCharacteristic = null; - mBatteryCharacteristic = null; - mBluetoothGatt = null; - } - } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManagerCallbacks.java index e08d41bc0..702c2337b 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManagerCallbacks.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/bpm/BPMManagerCallbacks.java @@ -29,16 +29,6 @@ public interface BPMManagerCallbacks extends BleManagerCallbacks { public static final int UNIT_mmHG = 0; public static final int UNIT_kPa = 1; - /** - * Called when the Blood Pressure Measurement characteristic indication has been enabled - */ - public void onBloodPressureMeasurementIndicationsEnabled(); - - /** - * Called when the Intermediate Cuff Pressure characteristic notification has been enabled - */ - public void onIntermediateCuffPressureNotificationEnabled(); - /** * Called when new BPM value has been obtained from the sensor * diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCActivity.java index ac7a0d2b3..284b396c8 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCActivity.java @@ -22,13 +22,6 @@ package no.nordicsemi.android.nrftoolbox.csc; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsActivity; -import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsFragment; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -40,6 +33,14 @@ import android.view.Menu; import android.widget.TextView; +import java.util.UUID; + +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsActivity; +import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsFragment; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity; + public class CSCActivity extends BleProfileServiceReadyActivity { private TextView mSpeedView; private TextView mSpeedUnitView; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCManager.java index d685d0972..c9c10c266 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCManager.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCManager.java @@ -22,188 +22,70 @@ package no.nordicsemi.android.nrftoolbox.csc; -import java.util.List; -import java.util.UUID; - -import no.nordicsemi.android.log.ILogSession; -import no.nordicsemi.android.log.Logger; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; -import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; - -public class CSCManager implements BleManager { - private static final String TAG = "RSCManager"; - private CSCManagerCallbacks mCallbacks; - private BluetoothGatt mBluetoothGatt; - private Context mContext; - private ILogSession mLogSession; +import java.util.LinkedList; +import java.util.Queue; +import java.util.UUID; - private static final byte WHEEL_REVOLUTIONS_DATA_PRESENT = 0x01; // 1 bit - private static final byte CRANK_REVOLUTION_DATA_PRESENT = 0x02; // 1 bit +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.parser.CSCMeasurementParser; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +public class CSCManager extends BleManager { + /** Cycling Speed and Cadence service UUID */ public final static UUID CYCLING_SPEED_AND_CADENCE_SERVICE_UUID = UUID.fromString("00001816-0000-1000-8000-00805f9b34fb"); - /** Cycling Speed and Cadence Measurement characteristic */ + /** Cycling Speed and Cadence Measurement characteristic UUID */ private static final UUID CSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A5B-0000-1000-8000-00805f9b34fb"); - private final static UUID BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb"); - /** Battery Level characteristic */ - private final static UUID BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb"); - /** Client configuration descriptor that will allow us to enable notifications and indications */ - private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); - - private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change"; - private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services"; - private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information"; - private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor"; - private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic"; + private static final byte WHEEL_REVOLUTIONS_DATA_PRESENT = 0x01; // 1 bit + private static final byte CRANK_REVOLUTION_DATA_PRESENT = 0x02; // 1 bit - private BluetoothGattCharacteristic mCSCMeasurementCharacteristic, mBatteryCharacteristic; - private boolean mBatteryLevelNotificationsEnabled; + private BluetoothGattCharacteristic mCSCMeasurementCharacteristic; public CSCManager(final Context context) { - // Register bonding broadcast receiver - final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - context.registerReceiver(mBondingBroadcastReceiver, filter); + super(context); } @Override - public void setGattCallbacks(final CSCManagerCallbacks callbacks) { - mCallbacks = callbacks; - } - - public void setLogger(final ILogSession session) { - mLogSession = session; - } - - @Override - public void connect(final Context context, final BluetoothDevice device) { - mContext = context; - - Logger.i(mLogSession, "[CSC] Gatt server started"); - if (mBluetoothGatt == null) { - mBluetoothGatt = device.connectGatt(mContext, true, mGattCallback); - } else { - mBluetoothGatt.connect(); - } - } - - @Override - public void disconnect() { - if (mBluetoothGatt != null) { - mBluetoothGatt.disconnect(); - } + protected BleManagerGattCallback getGattCallback() { + return mGattCallback; } /** * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc */ - private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { - @Override - public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (newState == BluetoothProfile.STATE_CONNECTED) { - DebugLogger.d(TAG, "Device connected"); - mBluetoothGatt.discoverServices(); - //This will send callback to RSCActivity when device get connected - mCallbacks.onDeviceConnected(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - DebugLogger.d(TAG, "Device disconnected"); - - // TODO It should check whether the user has requested disconnection or was it link loss. On Samsung S4 the DevKit reconnects itself just after linkloss but the Service is already dead. - mCallbacks.onDeviceDisconnected(); - closeBluetoothGatt(); - } - } else { - mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); - } - } + private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() { @Override - public void onServicesDiscovered(final BluetoothGatt gatt, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - final List services = gatt.getServices(); - for (BluetoothGattService service : services) { - if (service.getUuid().equals(CYCLING_SPEED_AND_CADENCE_SERVICE_UUID)) { - DebugLogger.d(TAG, "Cycling Speed and Cadence service is found"); - mCSCMeasurementCharacteristic = service.getCharacteristic(CSC_MEASUREMENT_CHARACTERISTIC_UUID); - } else if (service.getUuid().equals(BATTERY_SERVICE_UUID)) { - DebugLogger.d(TAG, "Battery service is found"); - mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID); - } - } - if (mCSCMeasurementCharacteristic == null) { - mCallbacks.onDeviceNotSupported(); - gatt.disconnect(); - } else { - mCallbacks.onServicesDiscovered(false /* more characteristics not supported */); - - // We have discovered services, let's start notifications and indications, one by one: battery, csc measurement - if (mBatteryCharacteristic != null) { - // Some devices has Battery Level characteristic without READ property - if ((mBatteryCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) - readBatteryLevel(gatt); - else if ((mBatteryCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) - enableBatteryLevelNotification(gatt); - else - enableCSCMeasurementNotification(gatt); - } else { - enableCSCMeasurementNotification(gatt); - } - } - } else { - mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status); - } + protected Queue initGatt(final BluetoothGatt gatt) { + final LinkedList requests = new LinkedList<>(); + requests.push(Request.newEnableNotificationsRequest(mCSCMeasurementCharacteristic)); + return requests; } @Override - public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) { - int batteryValue = characteristic.getValue()[0]; - mCallbacks.onBatteryValueReceived(batteryValue); - - if (!mBatteryLevelNotificationsEnabled) - enableCSCMeasurementNotification(gatt); - } - } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { - if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) { - DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); - mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); - } - } else { - mCallbacks.onError(ERROR_READ_CHARACTERISTIC, status); + public boolean isRequiredServiceSupported(final BluetoothGatt gatt) { + final BluetoothGattService service = gatt.getService(CYCLING_SPEED_AND_CADENCE_SERVICE_UUID); + if (service != null) { + mCSCMeasurementCharacteristic = service.getCharacteristic(CSC_MEASUREMENT_CHARACTERISTIC_UUID); } + return mCSCMeasurementCharacteristic != null; } @Override - public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (descriptor.getCharacteristic().getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) { - enableCSCMeasurementNotification(gatt); - } - } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { - if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) { - DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); - mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); - } - } else { - mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status); - } + protected void onDeviceDisconnected() { + mCSCMeasurementCharacteristic = null; } @Override - public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + public void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + if (mLogSession != null) + Logger.a(mLogSession, CSCMeasurementParser.parse(characteristic)); + // Decode the new data int offset = 0; final int flags = characteristic.getValue()[offset]; // 1 byte @@ -212,110 +94,27 @@ public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGat final boolean wheelRevPresent = (flags & WHEEL_REVOLUTIONS_DATA_PRESENT) > 0; final boolean crankRevPreset = (flags & CRANK_REVOLUTION_DATA_PRESENT) > 0; - int wheelRevolutions = 0; - int lastWheelEventTime = 0; if (wheelRevPresent) { - wheelRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, offset); + final int wheelRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, offset); offset += 4; - lastWheelEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); // 1/1024 s + final int lastWheelEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); // 1/1024 s offset += 2; // Notify listener about the new measurement mCallbacks.onWheelMeasurementReceived(wheelRevolutions, lastWheelEventTime); } - int crankRevolutions = 0; - int lastCrankEventTime = 0; if (crankRevPreset) { - crankRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + final int crankRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); offset += 2; - lastCrankEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); - offset += 2; + final int lastCrankEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + // offset += 2; // Notify listener about the new measurement mCallbacks.onCrankMeasurementReceived(crankRevolutions, lastCrankEventTime); } } }; - - private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); - final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); - - // skip other devices - if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress())) - return; - - DebugLogger.i(TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState); - - if (bondState == BluetoothDevice.BOND_BONDING) { - mCallbacks.onBondingRequired(); - return; - } - if (bondState == BluetoothDevice.BOND_BONDED) { - mCallbacks.onBonded(); - } - } - }; - - public void readBatteryLevel() { - readBatteryLevel(mBluetoothGatt); - } - - /** - * Reading the current value of the battery level - */ - private void readBatteryLevel(final BluetoothGatt gatt) { - if (mBatteryCharacteristic != null) { - if ((mBatteryCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) { - DebugLogger.d(TAG, "reading battery characteristic"); - gatt.readCharacteristic(mBatteryCharacteristic); - } - } else { - DebugLogger.w(TAG, "Battery Level Characteristic is null"); - } - } - - /** - * Enabling notification on Battery Level Characteristic - */ - private void enableBatteryLevelNotification(final BluetoothGatt gatt) { - mBatteryLevelNotificationsEnabled = true; - gatt.setCharacteristicNotification(mBatteryCharacteristic, true); - final BluetoothGattDescriptor descriptor = mBatteryCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(descriptor); - } - - /** - * Enabling notification on CSC Measurement Characteristic - */ - private void enableCSCMeasurementNotification(final BluetoothGatt gatt) { - gatt.setCharacteristicNotification(mCSCMeasurementCharacteristic, true); - final BluetoothGattDescriptor descriptor = mCSCMeasurementCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(descriptor); - } - - @Override - public void closeBluetoothGatt() { - try { - mContext.unregisterReceiver(mBondingBroadcastReceiver); - } catch (Exception e) { - // the receiver must have been not registered or unregistered before - } - if (mBluetoothGatt != null) { - mBluetoothGatt.close(); - mBluetoothGatt = null; - } - mBatteryLevelNotificationsEnabled = false; - mCallbacks = null; - mLogSession = null; - } - } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java index 61921c758..ca5ee6b98 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/CSCService.java @@ -22,12 +22,6 @@ package no.nordicsemi.android.nrftoolbox.csc; -import no.nordicsemi.android.log.Logger; -import no.nordicsemi.android.nrftoolbox.FeaturesActivity; -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsFragment; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -36,10 +30,16 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.os.IBinder; import android.preference.PreferenceManager; import android.support.v4.content.LocalBroadcastManager; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.FeaturesActivity; +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.csc.settings.SettingsFragment; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; + public class CSCService extends BleProfileService implements CSCManagerCallbacks { private static final String TAG = "CSCService"; @@ -57,7 +57,6 @@ public class CSCService extends BleProfileService implements CSCManagerCallbacks private static final String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.csc.ACTION_DISCONNECT"; private CSCManager mManager; - private boolean mBinded; private int mFirstWheelRevolutions = -1; private int mLastWheelRevolutions = -1; @@ -73,7 +72,7 @@ public class CSCService extends BleProfileService implements CSCManagerCallbacks private final LocalBinder mBinder = new CSCBinder(); /** - * This local binder is an interface for the binded activity to operate with the RSC sensor + * This local binder is an interface for the bonded activity to operate with the RSC sensor */ public class CSCBinder extends LocalBinder { // empty @@ -108,28 +107,15 @@ public void onDestroy() { } @Override - public IBinder onBind(final Intent intent) { - mBinded = true; - return super.onBind(intent); - } - - @Override - public void onRebind(final Intent intent) { - mBinded = true; + protected void onRebind() { // when the activity rebinds to the service, remove the notification cancelNotification(); - - // read the battery level when back in the Activity - if (isConnected()) - mManager.readBatteryLevel(); } @Override - public boolean onUnbind(final Intent intent) { - mBinded = false; - // when the activity closes we need to show the notification that user is connected to the sensor + protected void onUnbind() { + // when the activity closes we need to show the notification that user is connected to the sensor createNotification(R.string.csc_notification_connected_message, 0); - return super.onUnbind(intent); } @Override @@ -140,6 +126,8 @@ protected void onServiceStarted() { @Override public void onWheelMeasurementReceived(final int wheelRevolutions, final int lastWheelEventTime) { + Logger.a(getLogSession(), "Wheel rev: " + wheelRevolutions + "\nLast wheel event time: " + lastWheelEventTime + " ms"); + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); final int circumference = Integer.parseInt(preferences.getString(SettingsFragment.SETTINGS_WHEEL_SIZE, String.valueOf(SettingsFragment.SETTINGS_WHEEL_SIZE_DEFAULT))); // [mm] @@ -173,6 +161,8 @@ public void onWheelMeasurementReceived(final int wheelRevolutions, final int las @Override public void onCrankMeasurementReceived(int crankRevolutions, int lastCrankEventTime) { + Logger.a(getLogSession(), "Crank rev: " + crankRevolutions + "\nLast crank event time: " + lastCrankEventTime + " ms"); + if (mLastCrankEventTime == lastCrankEventTime) return; @@ -241,7 +231,7 @@ private void cancelNotification() { private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { - Logger.i(getLogSession(), "[CSC] Disconnect action pressed"); + Logger.i(getLogSession(), "[Notification] Disconnect action pressed"); if (isConnected()) getBinder().disconnect(); else diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/settings/SettingsFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/settings/SettingsFragment.java index 559b73e50..590b18629 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/settings/SettingsFragment.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc/settings/SettingsFragment.java @@ -22,12 +22,13 @@ package no.nordicsemi.android.nrftoolbox.csc.settings; -import no.nordicsemi.android.nrftoolbox.R; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; +import no.nordicsemi.android.nrftoolbox.R; + public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { public static final String SETTINGS_WHEEL_SIZE = "settings_wheel_size"; public static final int SETTINGS_WHEEL_SIZE_DEFAULT = 2340; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuActivity.java index 69318e570..94ef3a1f9 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuActivity.java @@ -72,6 +72,7 @@ import no.nordicsemi.android.nrftoolbox.dfu.fragment.UploadCancelFragment; import no.nordicsemi.android.nrftoolbox.dfu.fragment.ZipInfoFragment; import no.nordicsemi.android.nrftoolbox.dfu.settings.SettingsActivity; +import no.nordicsemi.android.nrftoolbox.dfu.settings.SettingsFragment; import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment; import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; @@ -85,7 +86,7 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks loader, final Cursor data) { private void updateFileInfo(final String fileName, final long fileSize, final int fileType) { mFileNameView.setText(fileName); switch (fileType) { - case DfuService.TYPE_SOFT_DEVICE: + case DfuService.TYPE_AUTO: mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[0]); break; - case DfuService.TYPE_BOOTLOADER: + case DfuService.TYPE_SOFT_DEVICE: mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[1]); break; - case DfuService.TYPE_APPLICATION: + case DfuService.TYPE_BOOTLOADER: mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[2]); break; - case DfuService.TYPE_AUTO: + case DfuService.TYPE_APPLICATION: mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[3]); break; } @@ -625,20 +632,20 @@ public void onSelectFileHelpClicked(final View view) { */ public void onSelectFileClicked(final View view) { mFileTypeTmp = mFileType; - int index = 2; + int index = 0; switch (mFileType) { - case DfuService.TYPE_SOFT_DEVICE: + case DfuService.TYPE_AUTO: index = 0; break; + case DfuService.TYPE_SOFT_DEVICE: + index = 1; + break; case DfuService.TYPE_BOOTLOADER: - index = 1; - break; + index = 2; + break; case DfuService.TYPE_APPLICATION: - index = 2; - break; - case DfuService.TYPE_AUTO: - index = 3; - break; + index = 3; + break; } // Show a dialog with file types new AlertDialog.Builder(this).setTitle(R.string.dfu_file_type_title) @@ -647,16 +654,16 @@ public void onSelectFileClicked(final View view) { public void onClick(final DialogInterface dialog, final int which) { switch (which) { case 0: - mFileTypeTmp = DfuService.TYPE_SOFT_DEVICE; + mFileTypeTmp = DfuService.TYPE_AUTO; break; case 1: - mFileTypeTmp = DfuService.TYPE_BOOTLOADER; + mFileTypeTmp = DfuService.TYPE_SOFT_DEVICE; break; case 2: - mFileTypeTmp = DfuService.TYPE_APPLICATION; + mFileTypeTmp = DfuService.TYPE_BOOTLOADER; break; case 3: - mFileTypeTmp = DfuService.TYPE_AUTO; + mFileTypeTmp = DfuService.TYPE_APPLICATION; break; } } @@ -734,6 +741,8 @@ public void onUploadClicked(final View view) { showProgressBar(); + final boolean keepBond = preferences.getBoolean(SettingsFragment.SETTINGS_KEEP_BOND, false); + final Intent service = new Intent(this, DfuService.class); service.putExtra(DfuService.EXTRA_DEVICE_ADDRESS, mSelectedDevice.getAddress()); service.putExtra(DfuService.EXTRA_DEVICE_NAME, mSelectedDevice.getName()); @@ -743,6 +752,7 @@ public void onUploadClicked(final View view) { service.putExtra(DfuService.EXTRA_FILE_URI, mFileStreamUri); service.putExtra(DfuService.EXTRA_INIT_FILE_PATH, mInitFilePath); service.putExtra(DfuService.EXTRA_INIT_FILE_URI, mInitFileStreamUri); + service.putExtra(DfuService.EXTRA_KEEP_BOND, keepBond); startService(service); } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuInitiatorActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuInitiatorActivity.java index 7f222b77d..9d2798f1b 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuInitiatorActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuInitiatorActivity.java @@ -22,12 +22,13 @@ package no.nordicsemi.android.nrftoolbox.dfu; -import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment; import android.app.Activity; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.os.Bundle; +import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment; + /** * The activity is started only by a remote connected computer using ADB. It shows a list of DFU-supported devices in range and allows user to select target device. The HEX file will be uploaded to * selected device using {@link DfuService}. @@ -58,6 +59,7 @@ public void onDeviceSelected(final BluetoothDevice device, final String name) { final String address = device.getAddress(); final String finalName = overwrittenName == null ? name : overwrittenName; final int type = intent.getIntExtra(DfuService.EXTRA_FILE_TYPE, DfuService.TYPE_AUTO); + final boolean keepBond = intent.getBooleanExtra(DfuService.EXTRA_KEEP_BOND, false); // Start DFU service with data provided in the intent final Intent service = new Intent(this, DfuService.class); @@ -67,6 +69,7 @@ public void onDeviceSelected(final BluetoothDevice device, final String name) { service.putExtra(DfuService.EXTRA_FILE_PATH, path); if (intent.hasExtra(DfuService.EXTRA_INIT_FILE_PATH)) service.putExtra(DfuService.EXTRA_INIT_FILE_PATH, initPath); + service.putExtra(DfuService.EXTRA_KEEP_BOND, keepBond); startService(service); finish(); } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuService.java index b569a967c..48a92cfd1 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuService.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/DfuService.java @@ -22,9 +22,10 @@ package no.nordicsemi.android.nrftoolbox.dfu; -import no.nordicsemi.android.dfu.DfuBaseService; import android.app.Activity; +import no.nordicsemi.android.dfu.DfuBaseService; + public class DfuService extends DfuBaseService { @Override diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/NotificationActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/NotificationActivity.java index c60347102..1b02671ae 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/NotificationActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/NotificationActivity.java @@ -22,11 +22,12 @@ package no.nordicsemi.android.nrftoolbox.dfu; -import no.nordicsemi.android.nrftoolbox.FeaturesActivity; import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import no.nordicsemi.android.nrftoolbox.FeaturesActivity; + public class NotificationActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/adapter/FileBrowserAppsAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/adapter/FileBrowserAppsAdapter.java index cfe2fba32..b21a28b8e 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/adapter/FileBrowserAppsAdapter.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/adapter/FileBrowserAppsAdapter.java @@ -21,7 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.dfu.adapter; -import no.nordicsemi.android.nrftoolbox.R; import android.content.Context; import android.content.res.Resources; import android.view.LayoutInflater; @@ -30,6 +29,8 @@ import android.widget.BaseAdapter; import android.widget.TextView; +import no.nordicsemi.android.nrftoolbox.R; + /** * This adapter displays some file browser applications that can be used to select HEX file. It is used when there is no such app already installed on the device. The hardcoded apps and Google Play * URLs are specified in res/values/strings_dfu.xml. diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/UploadCancelFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/UploadCancelFragment.java index 3f6531895..a9df2d39f 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/UploadCancelFragment.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/UploadCancelFragment.java @@ -21,8 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.dfu.fragment; -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.dfu.DfuService; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -33,6 +31,9 @@ import android.support.v4.content.LocalBroadcastManager; import android.util.Log; +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.dfu.DfuService; + /** * When cancel button is pressed during uploading this fragment shows uploading cancel dialog */ diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/ZipInfoFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/ZipInfoFragment.java index e3b6a3eca..f3ccd93d8 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/ZipInfoFragment.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/fragment/ZipInfoFragment.java @@ -21,7 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.dfu.fragment; -import no.nordicsemi.android.nrftoolbox.R; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -29,6 +28,8 @@ import android.view.LayoutInflater; import android.view.View; +import no.nordicsemi.android.nrftoolbox.R; + public class ZipInfoFragment extends DialogFragment { @Override diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/AboutDfuPreference.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/AboutDfuPreference.java index 6c8189650..24e860ed5 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/AboutDfuPreference.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/AboutDfuPreference.java @@ -22,7 +22,6 @@ package no.nordicsemi.android.nrftoolbox.dfu.settings; -import no.nordicsemi.android.nrftoolbox.R; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -30,6 +29,8 @@ import android.util.AttributeSet; import android.widget.Toast; +import no.nordicsemi.android.nrftoolbox.R; + public class AboutDfuPreference extends Preference { public AboutDfuPreference(Context context, AttributeSet attrs) { @@ -43,7 +44,7 @@ public AboutDfuPreference(Context context, AttributeSet attrs, int defStyle) { @Override protected void onClick() { final Context context = getContext(); - final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://developer.nordicsemi.com/nRF51_SDK/doc/7.1.0/s110/html/a00062.html")); + final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://developer.nordicsemi.com/nRF51_SDK/doc/8.0.0/s110/html/a00078.html")); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/SettingsFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/SettingsFragment.java index c98d191c3..b939ce91c 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/SettingsFragment.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/dfu/settings/SettingsFragment.java @@ -21,15 +21,17 @@ */ package no.nordicsemi.android.nrftoolbox.dfu.settings; -import no.nordicsemi.android.dfu.DfuSettingsConstants; -import no.nordicsemi.android.nrftoolbox.R; import android.app.AlertDialog; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; +import no.nordicsemi.android.dfu.DfuSettingsConstants; +import no.nordicsemi.android.nrftoolbox.R; + public class SettingsFragment extends PreferenceFragment implements DfuSettingsConstants, SharedPreferences.OnSharedPreferenceChangeListener { + public static final String SETTINGS_KEEP_BOND = "settings_keep_bond"; @Override public void onCreate(final Bundle savedInstanceState) { diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/ExpandableRecordAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/ExpandableRecordAdapter.java index a4bc807d7..58703897a 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/ExpandableRecordAdapter.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/ExpandableRecordAdapter.java @@ -21,7 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.gls; -import no.nordicsemi.android.nrftoolbox.R; import android.content.Context; import android.content.res.Resources; import android.util.Pair; @@ -31,6 +30,8 @@ import android.widget.BaseExpandableListAdapter; import android.widget.TextView; +import no.nordicsemi.android.nrftoolbox.R; + public class ExpandableRecordAdapter extends BaseExpandableListAdapter { private final GlucoseManager mGlucoseManager; private final LayoutInflater mInflater; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseActivity.java index 0021b1946..06e1132ac 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseActivity.java @@ -21,11 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.gls; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileExpandableListActivity; import android.os.Bundle; import android.util.SparseArray; import android.view.MenuInflater; @@ -35,6 +30,13 @@ import android.widget.PopupMenu; import android.widget.TextView; +import java.util.UUID; + +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileExpandableListActivity; + +// TODO The GlucoseActivity should be rewritten to use the service approach, like other do. public class GlucoseActivity extends BleProfileExpandableListActivity implements PopupMenu.OnMenuItemClickListener, GlucoseManagerCallbacks { @SuppressWarnings("unused") private static final String TAG = "GlucoseActivity"; @@ -95,7 +97,7 @@ public void onClick(View v) { @Override protected BleManager initializeManager() { - GlucoseManager manager = mGlucoseManager = GlucoseManager.getGlucoseManager(); + GlucoseManager manager = mGlucoseManager = GlucoseManager.getGlucoseManager(getApplicationContext()); manager.setGattCallbacks(this); return manager; } @@ -119,6 +121,11 @@ public boolean onMenuItemClick(final MenuItem item) { return true; } + @Override + protected int getLoggerProfileTitle() { + return R.string.gls_feature_title; + } + @Override protected int getAboutTextId() { return R.string.gls_about_text; @@ -151,23 +158,9 @@ public void run() { } @Override - public void onServicesDiscovered(boolean optionalServicesFound) { - // this may notify user or show some views - } - - @Override - public void onGlucoseMeasurementNotificationEnabled() { - // this may notify user or show some views - } - - @Override - public void onGlucoseMeasurementContextNotificationEnabled() { - // this may notify user or show some views - } - - @Override - public void onRecordAccessControlPointIndicationsEnabled() { - // this may notify user or show some views + public void onDeviceDisconnected() { + super.onDeviceDisconnected(); + setOperationInProgress(false); } @Override @@ -197,6 +190,12 @@ public void onOperationFailed() { showToast(R.string.gls_operation_failed); } + @Override + public void onError(final String message, final int errorCode) { + super.onError(message, errorCode); + onOperationFailed(); + } + @Override public void onDatasetChanged() { runOnUiThread(new Runnable() { diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManager.java index a734e845d..6f20c3e94 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManager.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManager.java @@ -21,51 +21,39 @@ */ package no.nordicsemi.android.nrftoolbox.gls; -import java.util.Calendar; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; -import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.Handler; import android.util.SparseArray; -@SuppressWarnings("unused") -public class GlucoseManager implements BleManager { - private static final String TAG = "GlucoseManager"; +import java.util.Calendar; +import java.util.LinkedList; +import java.util.Queue; +import java.util.UUID; - private GlucoseManagerCallbacks mCallbacks; - private BluetoothGatt mBluetoothGatt; - private Context mContext; - private Handler mHandler; - private boolean mAbort; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.parser.GlucoseMeasurementContextParser; +import no.nordicsemi.android.nrftoolbox.parser.GlucoseMeasurementParser; +import no.nordicsemi.android.nrftoolbox.parser.RecordAccessControlPointParser; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; - private final SparseArray mRecords = new SparseArray<>(); +@SuppressWarnings("unused") +public class GlucoseManager extends BleManager { + private static final String TAG = "GlucoseManager"; + /** Glucose service UUID */ public final static UUID GLS_SERVICE_UUID = UUID.fromString("00001808-0000-1000-8000-00805f9b34fb"); - public final static UUID BATTERY_SERVICE = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb"); - /** Glucose Measurement characteristic */ + /** Glucose Measurement characteristic UUID */ private final static UUID GM_CHARACTERISTIC = UUID.fromString("00002A18-0000-1000-8000-00805f9b34fb"); - /** Glucose Measurement Context characteristic */ + /** Glucose Measurement Context characteristic UUID */ private final static UUID GM_CONTEXT_CHARACTERISTIC = UUID.fromString("00002A34-0000-1000-8000-00805f9b34fb"); - /** Glucose Feature characteristic */ + /** Glucose Feature characteristic UUID */ private final static UUID GF_CHARACTERISTIC = UUID.fromString("00002A51-0000-1000-8000-00805f9b34fb"); - /** Record Access Control Point characteristic */ + /** Record Access Control Point characteristic UUID */ private final static UUID RACP_CHARACTERISTIC = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb"); - /** Battery Level characteristic */ - private final static UUID BATTERY_LEVEL_CHARACTERISTIC = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb"); - /** Client configuration descriptor that will allow us to enable notifications and indications */ - private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); private final static int OP_CODE_REPORT_STORED_RECORDS = 1; private final static int OP_CODE_DELETE_STORED_RECORDS = 2; @@ -105,314 +93,80 @@ public class GlucoseManager implements BleManager { private final static int RESPONSE_PROCEDURE_NOT_COMPLETED = 8; private final static int RESPONSE_OPERAND_NOT_SUPPORTED = 9; - private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change"; - private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services"; - private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor"; - private final static String ERROR_WRITE_CHARACTERISTIC = "Error on writing characteristic"; - private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information"; - private BluetoothGattCharacteristic mGlucoseMeasurementCharacteristic; - private BluetoothGattCharacteristic mGlucoseFeatureCharacteristic; private BluetoothGattCharacteristic mGlucoseMeasurementContextCharacteristic; private BluetoothGattCharacteristic mRecordAccessControlPointCharacteristic; - private BluetoothGattCharacteristic mBatteryLevelCharacteristic; + private final SparseArray mRecords = new SparseArray<>(); + private boolean mAbort; + private Handler mHandler; private static GlucoseManager mInstance; /** * Returns the singleton implementation of GlucoseManager */ - public static GlucoseManager getGlucoseManager() { + public static GlucoseManager getGlucoseManager(final Context context) { if (mInstance == null) - mInstance = new GlucoseManager(); + mInstance = new GlucoseManager(context); return mInstance; } - /** - * Callbacks for activity {@link GlucoseActivity} that implements {@link GlucoseManagerCallbacks} interface activity use this method to register itself for receiving callbacks - */ - @Override - public void setGattCallbacks(final GlucoseManagerCallbacks callbacks) { - mCallbacks = callbacks; + public GlucoseManager(final Context context) { + super(context); + mHandler = new Handler(); } - /** - * Returns all records as a sparse array where sequence number is the key. - * - * @return the records list - */ - public SparseArray getRecords() { - return mRecords; - } - - @Override - public void connect(final Context context, final BluetoothDevice device) { - if (mHandler == null) - mHandler = new Handler(); - mContext = context; - - // Register bonding broadcast receiver - final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - context.registerReceiver(mBondingBroadcastReceiver, filter); - - mBluetoothGatt = device.connectGatt(context, false, mGattCallback); - } - - /** - * Disable HR notification first and then disconnect to HR device - */ @Override - public void disconnect() { - if (mBluetoothGatt != null) { - mBluetoothGatt.disconnect(); - } - } - - /** - * Clears the records list locally - */ - public void clear() { - mRecords.clear(); - mCallbacks.onDatasetChanged(); - } - - /** - * Sends the request to obtain the last (most recent) record from glucose device. The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access - * Control Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error. - */ - public void getLastRecord() { - if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null) - return; - - clear(); - mCallbacks.onOperationStarted(); - - final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; - setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_LAST_RECORD); - mBluetoothGatt.writeCharacteristic(characteristic); - } - - /** - * Sends the request to obtain the first (oldest) record from glucose device. The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access Control - * Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error. - */ - public void getFirstRecord() { - if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null) - return; - - clear(); - mCallbacks.onOperationStarted(); - - final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; - setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_FIRST_RECORD); - mBluetoothGatt.writeCharacteristic(characteristic); - } - - /** - * Sends the request to obtain all records from glucose device. Initially we want to notify him/her about the number of the records so the {@link #OP_CODE_REPORT_NUMBER_OF_RECORDS} is send. The - * data will be returned to Glucose Measurement characteristic as a notification followed by Record Access Control Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of - * error. - */ - public void getAllRecords() { - if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null) - return; - - clear(); - mCallbacks.onOperationStarted(); - - final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; - setOpCode(characteristic, OP_CODE_REPORT_NUMBER_OF_RECORDS, OPERATOR_ALL_RECORDS); - mBluetoothGatt.writeCharacteristic(characteristic); + protected BleManagerGattCallback getGattCallback() { + return mGattCallback; } /** - * Sends the request to obtain from the glucose device all records newer than the newest one from local storage. The data will be returned to Glucose Measurement characteristic as a notification - * followed by Record Access Control Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error. - *

- * Refresh button will not download records older than the oldest in the local memory. F.e. if you have pressed Last and then Refresh, than it will try to get only newer records. However if there - * are no records, it will download all existing (using {@link #getAllRecords()}). - *

- */ - public void refreshRecords() { - if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null) - return; - - if (mRecords.size() == 0) { - getAllRecords(); - } else { - mCallbacks.onOperationStarted(); - - // obtain the last sequence number - final int sequenceNumber = mRecords.keyAt(mRecords.size() - 1) + 1; - - final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; - setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_GREATER_THEN_OR_EQUAL, sequenceNumber); - mBluetoothGatt.writeCharacteristic(characteristic); - // Info: - // Operators OPERATOR_LESS_THEN_OR_EQUAL and OPERATOR_RANGE are not supported by Nordic Semiconductor Glucose Service in SDK 4.4.2. - } - } - - /** - * Sends abort operation signal to the device - */ - public void abort() { - if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null) - return; - - mAbort = true; - final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; - setOpCode(characteristic, OP_CODE_ABORT_OPERATION, OPERATOR_NULL); - mBluetoothGatt.writeCharacteristic(characteristic); - } - - /** - * Sends the request to delete all data from the device. A Record Access Control Point indication with status code ({@link #RESPONSE_SUCCESS} (or other in case of error) will be send. - * - * @FIXME This method is not supported by Nordic Semiconductor Glucose Service in SDK 4.4.2. + * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc */ - public void deleteAllRecords() { - if (mBluetoothGatt == null || mRecordAccessControlPointCharacteristic == null) - return; - - clear(); - mCallbacks.onOperationStarted(); - - final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; - setOpCode(characteristic, OP_CODE_DELETE_STORED_RECORDS, OPERATOR_ALL_RECORDS); - mBluetoothGatt.writeCharacteristic(characteristic); - } + private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() { - private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() { @Override - public void onReceive(final Context context, final Intent intent) { - final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); - final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); - - DebugLogger.d(TAG, "Bond state changed for: " + device.getAddress() + " new state: " + bondState + " previous: " + previousBondState); - - // skip other devices - if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress())) - return; - - if (bondState == BluetoothDevice.BOND_BONDING) { - mCallbacks.onBondingRequired(); - } else if (bondState == BluetoothDevice.BOND_BONDED) { - // We've read Battery Level, now let'so enable notifications and indication - enableGlucoseMeasurementNotification(mBluetoothGatt); - mCallbacks.onBonded(); - } + protected Queue initGatt(final BluetoothGatt gatt) { + final LinkedList requests = new LinkedList<>(); + requests.push(Request.newEnableNotificationsRequest(mGlucoseMeasurementCharacteristic)); + if (mGlucoseMeasurementContextCharacteristic != null) + requests.push(Request.newEnableNotificationsRequest(mGlucoseMeasurementContextCharacteristic)); + requests.push(Request.newEnableIndicationsRequest(mRecordAccessControlPointCharacteristic)); + return requests; } - }; - /** - * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc - */ - private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override - public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (newState == BluetoothProfile.STATE_CONNECTED) { - mCallbacks.onDeviceConnected(); - // start discovering services - gatt.discoverServices(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - mCallbacks.onDeviceDisconnected(); - gatt.close(); - } - } else { - DebugLogger.e(TAG, "onConnectionStateChange error " + status); - mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); + public boolean isRequiredServiceSupported(final BluetoothGatt gatt) { + final BluetoothGattService service = gatt.getService(GLS_SERVICE_UUID); + if (service != null) { + mGlucoseMeasurementCharacteristic = service.getCharacteristic(GM_CHARACTERISTIC); + mGlucoseMeasurementContextCharacteristic = service.getCharacteristic(GM_CONTEXT_CHARACTERISTIC); + mRecordAccessControlPointCharacteristic = service.getCharacteristic(RACP_CHARACTERISTIC); } + return mGlucoseMeasurementCharacteristic != null && mRecordAccessControlPointCharacteristic != null; } @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - for (BluetoothGattService service : gatt.getServices()) { - if (GLS_SERVICE_UUID.equals(service.getUuid())) { - mGlucoseMeasurementCharacteristic = service.getCharacteristic(GM_CHARACTERISTIC); - mGlucoseMeasurementContextCharacteristic = service.getCharacteristic(GM_CONTEXT_CHARACTERISTIC); - mGlucoseFeatureCharacteristic = service.getCharacteristic(GF_CHARACTERISTIC); - mRecordAccessControlPointCharacteristic = service.getCharacteristic(RACP_CHARACTERISTIC); - } else if (BATTERY_SERVICE.equals(service.getUuid())) { - mBatteryLevelCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC); - } - } - // Validate the device for required characteristics - if (mGlucoseMeasurementCharacteristic == null || mRecordAccessControlPointCharacteristic == null) { - mCallbacks.onDeviceNotSupported(); - gatt.disconnect(); - return; - } - mCallbacks.onServicesDiscovered(mGlucoseMeasurementContextCharacteristic != null); - - // We have discovered services, let's start notifications and indications, one by one: read battery, enable GM, GMP (if exists) and RACP - if (mBatteryLevelCharacteristic != null) { - readBatteryLevel(gatt); - } else { - // this characteristic is mandatory - enableGlucoseMeasurementNotification(gatt); - } - } else { - DebugLogger.e(TAG, "onServicesDiscovered error " + status); - mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status); - } - } - - @Override - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (BATTERY_LEVEL_CHARACTERISTIC.equals(characteristic.getUuid())) { - final int batteryValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0); - mCallbacks.onBatteryValueReceived(batteryValue); - - // We've read Battery Level, now let'so enable ICP notifications or BPM indications - enableGlucoseMeasurementNotification(gatt); - } - } else { - DebugLogger.e(TAG, "onCharacteristicRead error " + status); - mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status); - } + protected boolean isOptionalServiceSupported(BluetoothGatt gatt) { + return mGlucoseMeasurementContextCharacteristic != null; } @Override - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (GM_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid())) { - mCallbacks.onGlucoseMeasurementNotificationEnabled(); - - if (mGlucoseMeasurementContextCharacteristic != null) { - enableGlucoseMeasurementContextNotification(gatt); - } else { - enableRecordAccessControlPointIndication(gatt); - } - } - - if (GM_CONTEXT_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid())) { - mCallbacks.onGlucoseMeasurementContextNotificationEnabled(); - enableRecordAccessControlPointIndication(gatt); - } - - if (RACP_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid())) { - mCallbacks.onRecordAccessControlPointIndicationsEnabled(); - } - } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { - if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) { - DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); - mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); - } - } else { - DebugLogger.e(TAG, "onDescriptorWrite error " + status); - mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status); - } + protected void onDeviceDisconnected() { + mGlucoseMeasurementCharacteristic = null; + mGlucoseMeasurementContextCharacteristic = null; + mRecordAccessControlPointCharacteristic = null; } @Override - public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + public void onCharacteristicNotified(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { final UUID uuid = characteristic.getUuid(); + if (GM_CHARACTERISTIC.equals(uuid)) { + if (mLogSession != null) + Logger.a(mLogSession, GlucoseMeasurementParser.parse(characteristic)); + int offset = 0; final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); offset += 1; @@ -487,6 +241,9 @@ public void run() { } }); } else if (GM_CONTEXT_CHARACTERISTIC.equals(uuid)) { + if (mLogSession != null) + Logger.a(mLogSession, GlucoseMeasurementContextParser.parse(characteristic)); + int offset = 0; final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); offset += 1; @@ -552,92 +309,62 @@ public void run() { // notify callback about the new record mCallbacks.onDatasetChanged(); - } else { // Record Access Control Point characteristic - int offset = 0; - final int opCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); - offset += 2; // skip the operator + } + } + + @Override + protected void onCharacteristicIndicated(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + if (mLogSession != null) + Logger.a(mLogSession, RecordAccessControlPointParser.parse(characteristic)); - if (opCode == OP_CODE_NUMBER_OF_STORED_RECORDS_RESPONSE) { - // We've obtained the number of all records - final int number = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + // Record Access Control Point characteristic + int offset = 0; + final int opCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + offset += 2; // skip the operator - mCallbacks.onNumberOfRecordsRequested(number); + if (opCode == OP_CODE_NUMBER_OF_STORED_RECORDS_RESPONSE) { + // We've obtained the number of all records + final int number = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); - // Request the records + mCallbacks.onNumberOfRecordsRequested(number); + + // Request the records + if (number > 0) { final BluetoothGattCharacteristic racpCharacteristic = mRecordAccessControlPointCharacteristic; setOpCode(racpCharacteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_ALL_RECORDS); - mBluetoothGatt.writeCharacteristic(racpCharacteristic); - } else if (opCode == OP_CODE_RESPONSE_CODE) { - final int requestedOpCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); - final int responseCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 1); - DebugLogger.d(TAG, "Response result for: " + requestedOpCode + " is: " + responseCode); - - switch (responseCode) { - case RESPONSE_SUCCESS: - if (!mAbort) - mCallbacks.onOperationCompleted(); - else - mCallbacks.onOperationAborted(); - break; - case RESPONSE_NO_RECORDS_FOUND: + writeCharacteristic(racpCharacteristic); + } else { + mCallbacks.onOperationCompleted(); + } + } else if (opCode == OP_CODE_RESPONSE_CODE) { + final int requestedOpCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + final int responseCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 1); + DebugLogger.d(TAG, "Response result for: " + requestedOpCode + " is: " + responseCode); + + switch (responseCode) { + case RESPONSE_SUCCESS: + if (!mAbort) mCallbacks.onOperationCompleted(); - break; - case RESPONSE_OP_CODE_NOT_SUPPORTED: - mCallbacks.onOperationNotSupported(); - break; - case RESPONSE_PROCEDURE_NOT_COMPLETED: - case RESPONSE_ABORT_UNSUCCESSFUL: - default: - mCallbacks.onOperationFailed(); - break; - } - mAbort = false; + else + mCallbacks.onOperationAborted(); + break; + case RESPONSE_NO_RECORDS_FOUND: + mCallbacks.onOperationCompleted(); + break; + case RESPONSE_OP_CODE_NOT_SUPPORTED: + mCallbacks.onOperationNotSupported(); + break; + case RESPONSE_PROCEDURE_NOT_COMPLETED: + case RESPONSE_ABORT_UNSUCCESSFUL: + default: + mCallbacks.onOperationFailed(); + break; } + mAbort = false; } } - - /** - * Reads battery level on the device - */ - private void readBatteryLevel(final BluetoothGatt gatt) { - DebugLogger.d(TAG, "readBatteryLevel()"); - gatt.readCharacteristic(mBatteryLevelCharacteristic); - } }; - /** - * Enabling notification on Glucose Measurement Characteristic - */ - private void enableGlucoseMeasurementNotification(final BluetoothGatt gatt) { - DebugLogger.d(TAG, "enableGlucoseMeasurementNotification()"); - gatt.setCharacteristicNotification(mGlucoseMeasurementCharacteristic, true); - final BluetoothGattDescriptor descriptor = mGlucoseMeasurementCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(descriptor); - } - - /** - * Enabling notification on Glucose Measurement Context Characteristic. This characteristic is optional - */ - private void enableGlucoseMeasurementContextNotification(final BluetoothGatt gatt) { - DebugLogger.d(TAG, "enableGlucoseMeasurementContextNotification()"); - gatt.setCharacteristicNotification(mGlucoseMeasurementContextCharacteristic, true); - final BluetoothGattDescriptor descriptor = mGlucoseMeasurementContextCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(descriptor); - } - - /** - * Enabling indications on Record Access Control Point Characteristic - */ - private void enableRecordAccessControlPointIndication(final BluetoothGatt gatt) { - DebugLogger.d(TAG, "enableGlucoseMeasurementContextNotification()"); - gatt.setCharacteristicNotification(mRecordAccessControlPointCharacteristic, true); - final BluetoothGattDescriptor descriptor = mRecordAccessControlPointCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); - gatt.writeDescriptor(descriptor); - } - /** * Writes given operation parameters to the characteristic * @@ -675,24 +402,127 @@ private void setOpCode(final BluetoothGattCharacteristic characteristic, final i } } } + /** + * Returns all records as a sparse array where sequence number is the key. + * + * @return the records list + */ + public SparseArray getRecords() { + return mRecords; + } - @Override - public void closeBluetoothGatt() { - try { - mContext.unregisterReceiver(mBondingBroadcastReceiver); - } catch (Exception e) { - // the receiver must have been not registered or unregistered before - } + /** + * Clears the records list locally + */ + public void clear() { + mRecords.clear(); + mCallbacks.onDatasetChanged(); + } - if (mBluetoothGatt != null) { - mBluetoothGatt.close(); - mRecords.clear(); - mGlucoseMeasurementCharacteristic = null; - mGlucoseMeasurementContextCharacteristic = null; - mGlucoseFeatureCharacteristic = null; - mRecordAccessControlPointCharacteristic = null; - mBatteryLevelCharacteristic = null; - mBluetoothGatt = null; + /** + * Sends the request to obtain the last (most recent) record from glucose device. The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access + * Control Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error. + */ + public void getLastRecord() { + if (mRecordAccessControlPointCharacteristic == null) + return; + + clear(); + mCallbacks.onOperationStarted(); + + final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; + setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_LAST_RECORD); + writeCharacteristic(characteristic); + } + + /** + * Sends the request to obtain the first (oldest) record from glucose device. The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access Control + * Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error. + */ + public void getFirstRecord() { + if (mRecordAccessControlPointCharacteristic == null) + return; + + clear(); + mCallbacks.onOperationStarted(); + + final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; + setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_FIRST_RECORD); + writeCharacteristic(characteristic); + } + + /** + * Sends the request to obtain all records from glucose device. Initially we want to notify him/her about the number of the records so the {@link #OP_CODE_REPORT_NUMBER_OF_RECORDS} is send. The + * data will be returned to Glucose Measurement characteristic as a notification followed by Record Access Control Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of + * error. + */ + public void getAllRecords() { + if (mRecordAccessControlPointCharacteristic == null) + return; + + clear(); + mCallbacks.onOperationStarted(); + + final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; + setOpCode(characteristic, OP_CODE_REPORT_NUMBER_OF_RECORDS, OPERATOR_ALL_RECORDS); + writeCharacteristic(characteristic); + } + + /** + * Sends the request to obtain from the glucose device all records newer than the newest one from local storage. The data will be returned to Glucose Measurement characteristic as a notification + * followed by Record Access Control Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error. + *

+ * Refresh button will not download records older than the oldest in the local memory. F.e. if you have pressed Last and then Refresh, than it will try to get only newer records. However if there + * are no records, it will download all existing (using {@link #getAllRecords()}). + *

+ */ + public void refreshRecords() { + if (mRecordAccessControlPointCharacteristic == null) + return; + + if (mRecords.size() == 0) { + getAllRecords(); + } else { + mCallbacks.onOperationStarted(); + + // obtain the last sequence number + final int sequenceNumber = mRecords.keyAt(mRecords.size() - 1) + 1; + + final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; + setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_GREATER_THEN_OR_EQUAL, sequenceNumber); + writeCharacteristic(characteristic); + // Info: + // Operators OPERATOR_LESS_THEN_OR_EQUAL and OPERATOR_RANGE are not supported by Nordic Semiconductor Glucose Service in SDK 4.4.2. } } + + /** + * Sends abort operation signal to the device + */ + public void abort() { + if (mRecordAccessControlPointCharacteristic == null) + return; + + mAbort = true; + final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; + setOpCode(characteristic, OP_CODE_ABORT_OPERATION, OPERATOR_NULL); + writeCharacteristic(characteristic); + } + + /** + * Sends the request to delete all data from the device. A Record Access Control Point indication with status code ({@link #RESPONSE_SUCCESS} (or other in case of error) will be send. + * + * @FIXME This method is not supported by Nordic Semiconductor Glucose Service in SDK 4.4.2. + */ + public void deleteAllRecords() { + if (mRecordAccessControlPointCharacteristic == null) + return; + + clear(); + mCallbacks.onOperationStarted(); + + final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; + setOpCode(characteristic, OP_CODE_DELETE_STORED_RECORDS, OPERATOR_ALL_RECORDS); + writeCharacteristic(characteristic); + } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManagerCallbacks.java index 83a2a3d6f..4e2affd36 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManagerCallbacks.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/gls/GlucoseManagerCallbacks.java @@ -27,12 +27,6 @@ public interface GlucoseManagerCallbacks extends BleManagerCallbacks { public static final int UNIT_mmHG = 0; public static final int UNIT_kPa = 1; - public void onGlucoseMeasurementNotificationEnabled(); - - public void onGlucoseMeasurementContextNotificationEnabled(); - - public void onRecordAccessControlPointIndicationsEnabled(); - public void onOperationStarted(); public void onOperationCompleted(); diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java index 7e987e297..7817a32ae 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java @@ -21,35 +21,36 @@ */ package no.nordicsemi.android.nrftoolbox.hrs; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileActivity; - -import org.achartengine.GraphicalView; - import android.graphics.Point; import android.os.Bundle; import android.os.Handler; import android.view.ViewGroup; import android.widget.TextView; +import org.achartengine.GraphicalView; + +import java.util.UUID; + +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileActivity; + /** * HRSActivity is the main Heart rate activity. It implements HRSManagerCallbacks to receive callbacks from HRSManager class. The activity supports portrait and landscape orientations. The activity * uses external library AChartEngine to show real time graph of HR values. */ +// TODO The HRSActivity should be rewritten to use the service approach, like other do. public class HRSActivity extends BleProfileActivity implements HRSManagerCallbacks { @SuppressWarnings("unused") private final String TAG = "HRSActivity"; - private static final String GRAPH_STATUS = "graph_status"; - private static final String GRAPH_COUNTER = "graph_counter"; - private static final String HR_VALUE = "hr_value"; + private final static String GRAPH_STATUS = "graph_status"; + private final static String GRAPH_COUNTER = "graph_counter"; + private final static String HR_VALUE = "hr_value"; - private final int MAX_HR_VALUE = 65535; - private final int MIN_POSITIVE_VALUE = 0; - private final int REFRESH_INTERVAL = 1000; // 1 second interval + private final static int MAX_HR_VALUE = 65535; + private final static int MIN_POSITIVE_VALUE = 0; + private final static int REFRESH_INTERVAL = 1000; // 1 second interval private Handler mHandler = new Handler(); @@ -63,7 +64,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac private int mCounter = 0; @Override - protected void onCreateView(Bundle savedInstanceState) { + protected void onCreateView(final Bundle savedInstanceState) { setContentView(R.layout.activity_feature_hrs); setGUI(); } @@ -82,7 +83,7 @@ private void showGraph() { } @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { + protected void onRestoreInstanceState(final Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState != null) { @@ -96,7 +97,7 @@ protected void onRestoreInstanceState(Bundle savedInstanceState) { } @Override - protected void onSaveInstanceState(Bundle outState) { + protected void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(GRAPH_STATUS, isGraphInProgress); @@ -111,6 +112,11 @@ protected void onDestroy() { stopShowGraph(); } + @Override + protected int getLoggerProfileTitle() { + return R.string.hrs_feature_title; + } + @Override protected int getAboutTextId() { return R.string.hrs_about_text; @@ -154,7 +160,7 @@ void stopShowGraph() { @Override protected BleManager initializeManager() { - HRSManager manager = HRSManager.getInstance(this); + final HRSManager manager = HRSManager.getInstance(getApplicationContext()); manager.setGattCallbacks(this); return manager; } @@ -186,18 +192,18 @@ public void run() { } @Override - public void onServicesDiscovered(boolean optionalServicesFound) { + public void onServicesDiscovered(final boolean optionalServicesFound) { // this may notify user or show some views } @Override - public void onHRSensorPositionFound(String position) { - setHRSPositionOnView(position); + public void onDeviceReady() { + startShowGraph(); } @Override - public void onHRNotificationEnabled() { - startShowGraph(); + public void onHRSensorPositionFound(final String position) { + setHRSPositionOnView(position); } @Override diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java index 4cf393e5b..25aafa5e4 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java @@ -21,231 +21,132 @@ */ package no.nordicsemi.android.nrftoolbox.hrs; -import java.util.List; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; -import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothProfile; import android.content.Context; +import java.util.LinkedList; +import java.util.Queue; +import java.util.UUID; + +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.parser.BodySensorLocationParser; +import no.nordicsemi.android.nrftoolbox.parser.HeartRateMeasurementParser; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; + /** * HRSManager class performs BluetoothGatt operations for connection, service discovery, enabling notification and reading characteristics. All operations required to connect to device with BLE HR * Service and reading heart rate values are performed here. HRSActivity implements HRSManagerCallbacks in order to receive callbacks of BluetoothGatt operations */ -public class HRSManager implements BleManager { - private final String TAG = "HRSManager"; - private HRSManagerCallbacks mCallbacks; - private BluetoothGatt mBluetoothGatt; - private Context mContext; - +public class HRSManager extends BleManager { public final static UUID HR_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb"); private static final UUID HR_SENSOR_LOCATION_CHARACTERISTIC_UUID = UUID.fromString("00002A38-0000-1000-8000-00805f9b34fb"); - private static final UUID HR_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb"); - private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); - - private final static UUID BATTERY_SERVICE = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb"); - private final static UUID BATTERY_LEVEL_CHARACTERISTIC = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb"); - private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change"; - private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services"; - private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor"; - private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic"; - - private BluetoothGattCharacteristic mHRCharacteristic, mHRLocationCharacteristic, mBatteryCharacteristic; - private static final int FIRST_BITMASK = 0x01; + private BluetoothGattCharacteristic mHRCharacteristic, mHRLocationCharacteristic; private static HRSManager managerInstance = null; /** * singleton implementation of HRSManager class */ - public static synchronized HRSManager getInstance(Context context) { + public static synchronized HRSManager getInstance(final Context context) { if (managerInstance == null) { - managerInstance = new HRSManager(); + managerInstance = new HRSManager(context); } - managerInstance.mContext = context; return managerInstance; } - /** - * callbacks for activity {HRSActivity} that implements HRSManagerCallbacks interface activity use this method to register itself for receiving callbacks - */ - @Override - public void setGattCallbacks(HRSManagerCallbacks callbacks) { - mCallbacks = callbacks; + public HRSManager(final Context context) { + super(context); } @Override - public void connect(Context context, BluetoothDevice device) { - DebugLogger.d(TAG, "Connecting to device..."); - mBluetoothGatt = device.connectGatt(context, false, mGattCallback); + protected BleManagerGattCallback getGattCallback() { + return mGattCallback; } /** - * Disable HR notification first and then disconnect to HR device + * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc */ - @Override - public void disconnect() { - DebugLogger.d(TAG, "Disconnecting device..."); - if (mBluetoothGatt != null) { - mBluetoothGatt.disconnect(); + private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() { + + @Override + protected Queue initGatt(final BluetoothGatt gatt) { + final LinkedList requests = new LinkedList<>(); + if (mHRLocationCharacteristic != null) + requests.push(Request.newReadRequest(mHRLocationCharacteristic)); + requests.push(Request.newEnableNotificationsRequest(mHRCharacteristic)); + return requests; } - } - /** - * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc - */ - private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override - public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (newState == BluetoothProfile.STATE_CONNECTED) { - DebugLogger.d(TAG, "Device connected"); - mBluetoothGatt.discoverServices(); - //This will send callback to HRSActivity when device get connected - mCallbacks.onDeviceConnected(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - DebugLogger.d(TAG, "Device disconnected"); - //This will send callback to HRSActivity when device get disconnected - mCallbacks.onDeviceDisconnected(); - } - } else { - mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); + protected boolean isRequiredServiceSupported(final BluetoothGatt gatt) { + final BluetoothGattService service = gatt.getService(HR_SERVICE_UUID); + if (service != null) { + mHRCharacteristic = service.getCharacteristic(HR_CHARACTERISTIC_UUID); } + return mHRCharacteristic != null; } @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - List services = gatt.getServices(); - for (BluetoothGattService service : services) { - if (service.getUuid().equals(HR_SERVICE_UUID)) { - mHRCharacteristic = service.getCharacteristic(HR_CHARACTERISTIC_UUID); - mHRLocationCharacteristic = service.getCharacteristic(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID); - } else if (service.getUuid().equals(BATTERY_SERVICE)) { - mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC); - } - } - if (mHRCharacteristic != null) { - //This will send callback to HRSActivity when HR Service is found in device - mCallbacks.onServicesDiscovered(false); - readHRSensorLocation(); - } else { - mCallbacks.onDeviceNotSupported(); - gatt.disconnect(); - } - } else { - mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status); + protected boolean isOptionalServiceSupported(final BluetoothGatt gatt) { + final BluetoothGattService service = gatt.getService(HR_SERVICE_UUID); + if (service != null) { + mHRLocationCharacteristic = service.getCharacteristic(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID); } + return mHRLocationCharacteristic != null; } @Override - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (characteristic.getUuid().equals(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID)) { - final String sensorPosition = getBodySensorPosition(characteristic.getValue()[0]); - //This will send callback to HRSActivity when HR sensor position on body is found in HR device - mCallbacks.onHRSensorPositionFound(sensorPosition); - - if (mBatteryCharacteristic != null) { - readBatteryLevel(); - } else { - enableHRNotification(); - } - } - if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC)) { - int batteryValue = characteristic.getValue()[0]; - //This will send callback to HRSActivity when Battery value is received from HR device - mCallbacks.onBatteryValueReceived(batteryValue); - - enableHRNotification(); - } - } else { - mCallbacks.onError(ERROR_READ_CHARACTERISTIC, status); - } + public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + if (mLogSession != null) + Logger.a(mLogSession, BodySensorLocationParser.parse(characteristic)); + + final String sensorPosition = getBodySensorPosition(characteristic.getValue()[0]); + //This will send callback to HRSActivity when HR sensor position on body is found in HR device + mCallbacks.onHRSensorPositionFound(sensorPosition); } @Override - public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - int hrValue; - //This will check if HR value is in 8 bits or 16 bits. - if (characteristic.getUuid().equals(HR_CHARACTERISTIC_UUID)) { - if (isHeartRateInUINT16(characteristic.getValue()[0])) { - hrValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1); - } else { - hrValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1); - } - //This will send callback to HRSActivity when new HR value is received from HR device - mCallbacks.onHRValueReceived(hrValue); - } + protected void onDeviceDisconnected() { + mHRLocationCharacteristic = null; + mHRCharacteristic = null; } @Override - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - //This will send callback to HRSActivity when HR notification is enabled - mCallbacks.onHRNotificationEnabled(); + public void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + if (mLogSession != null) + Logger.a(mLogSession, HeartRateMeasurementParser.parse(characteristic)); + + int hrValue; + if (isHeartRateInUINT16(characteristic.getValue()[0])) { + hrValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1); } else { - mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status); + hrValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1); } + //This will send callback to HRSActivity when new HR value is received from HR device + mCallbacks.onHRValueReceived(hrValue); } }; - private void readBatteryLevel() { - if (mBatteryCharacteristic != null) { - mBluetoothGatt.readCharacteristic(mBatteryCharacteristic); - } - } - - private void readHRSensorLocation() { - if (mHRLocationCharacteristic != null) { - mBluetoothGatt.readCharacteristic(mHRLocationCharacteristic); - } - } - /** * This method will decode and return Heart rate sensor position on body */ - private String getBodySensorPosition(byte bodySensorPositionValue) { - String[] locations = mContext.getResources().getStringArray(R.array.hrs_locations); + private String getBodySensorPosition(final byte bodySensorPositionValue) { + final String[] locations = getContext().getResources().getStringArray(R.array.hrs_locations); if (bodySensorPositionValue > locations.length) - return mContext.getString(R.string.hrs_location_other); + return getContext().getString(R.string.hrs_location_other); return locations[bodySensorPositionValue]; } /** * This method will check if Heart rate value is in 8 bits or 16 bits */ - private boolean isHeartRateInUINT16(byte value) { - return ((value & FIRST_BITMASK) != 0); - } - - /** - * Enabling notification on Heart Rate Characteristic - */ - private void enableHRNotification() { - DebugLogger.d(TAG, "Enabling heart rate notifications"); - mBluetoothGatt.setCharacteristicNotification(mHRCharacteristic, true); - BluetoothGattDescriptor descriptor = mHRCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - mBluetoothGatt.writeDescriptor(descriptor); - } - - @Override - public void closeBluetoothGatt() { - if (mBluetoothGatt != null) { - mBluetoothGatt.close(); - mBluetoothGatt = null; - } + private boolean isHeartRateInUINT16(final byte value) { + return ((value & 0x01) != 0); } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java index ab083e6e1..3fe7597a0 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java @@ -25,11 +25,6 @@ public interface HRSManagerCallbacks extends BleManagerCallbacks { - /** - * Called when Heart Rate notifications has been enabled - */ - public void onHRNotificationEnabled(); - /** * Called when the sensor position information has been obtained from the sensor * diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/LineGraphView.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/LineGraphView.java index c22082343..b3844c688 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/LineGraphView.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/LineGraphView.java @@ -21,6 +21,11 @@ */ package no.nordicsemi.android.nrftoolbox.hrs; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Paint.Align; +import android.graphics.Point; + import org.achartengine.ChartFactory; import org.achartengine.GraphicalView; import org.achartengine.chart.PointStyle; @@ -29,11 +34,6 @@ import org.achartengine.renderer.XYMultipleSeriesRenderer; import org.achartengine.renderer.XYSeriesRenderer; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Paint.Align; -import android.graphics.Point; - /** * This class uses external library AChartEngine to show dynamic real time line graph for HR values */ diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSActivity.java index 752aa2e3e..d3bcb43b8 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSActivity.java @@ -21,15 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.hts; -import java.text.DecimalFormat; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.hts.settings.SettingsFragment; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity; -import no.nordicsemi.android.nrftoolbox.hts.settings.SettingsActivity; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -41,6 +32,15 @@ import android.view.Menu; import android.widget.TextView; +import java.text.DecimalFormat; +import java.util.UUID; + +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.hts.settings.SettingsActivity; +import no.nordicsemi.android.nrftoolbox.hts.settings.SettingsFragment; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity; + /** * HTSActivity is the main Health Thermometer activity. It implements {@link HTSManagerCallbacks} to receive callbacks from {@link HTSManager} class. The activity supports portrait and landscape * orientations. @@ -136,6 +136,11 @@ protected void onServiceUnbinded() { // not used } + @Override + protected int getLoggerProfileTitle() { + return R.string.hts_feature_title; + } + @Override protected int getAboutTextId() { return R.string.hts_about_text; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSManager.java index a8666e4fd..288e0b986 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSManager.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSManager.java @@ -21,48 +21,33 @@ */ package no.nordicsemi.android.nrftoolbox.hts; -import java.util.List; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; -import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.UUID; + +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.parser.TemperatureMeasurementParser; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; /** * HTSManager class performs BluetoothGatt operations for connection, service discovery, enabling indication and reading characteristics. All operations required to connect to device with BLE HT * Service and reading health thermometer values are performed here. HTSActivity implements HTSManagerCallbacks in order to receive callbacks of BluetoothGatt operations */ -public class HTSManager implements BleManager { - private final String TAG = "HTSManager"; - private HTSManagerCallbacks mCallbacks; - private BluetoothGatt mBluetoothGatt; - private Context mContext; +public class HTSManager extends BleManager { + private static final String TAG = "HTSManager"; + /** Health Thermometer service UUID */ public final static UUID HT_SERVICE_UUID = UUID.fromString("00001809-0000-1000-8000-00805f9b34fb"); - + /** Health Thermometer Measurement characteristic UUID */ private static final UUID HT_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A1C-0000-1000-8000-00805f9b34fb"); - private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); - - private final static UUID BATTERY_SERVICE = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb"); - private final static UUID BATTERY_LEVEL_CHARACTERISTIC = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb"); - private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change"; - private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services"; - private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic"; - private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor"; - private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information"; - - private BluetoothGattCharacteristic mHTCharacteristic, mBatteryCharacteristic; + private BluetoothGattCharacteristic mHTCharacteristic; private final static int HIDE_MSB_8BITS_OUT_OF_32BITS = 0x00FFFFFF; private final static int HIDE_MSB_8BITS_OUT_OF_16BITS = 0x00FF; @@ -71,178 +56,55 @@ public class HTSManager implements BleManager { private final static int GET_BIT24 = 0x00400000; private final static int FIRST_BIT_MASK = 0x01; - private static HTSManager managerInstance = null; - - /** - * singleton implementation of HTSManager class - */ - public static synchronized HTSManager getHTSManager() { - if (managerInstance == null) { - managerInstance = new HTSManager(); - } - return managerInstance; - } - - /** - * callbacks for activity {HTSActivity} that implements HTSManagerCallbacks interface activity use this method to register itself for receiving callbacks - */ - @Override - public void setGattCallbacks(HTSManagerCallbacks callbacks) { - mCallbacks = callbacks; - } - - @Override - public void connect(Context context, BluetoothDevice device) { - mBluetoothGatt = device.connectGatt(context, false, mGattCallback); - mContext = context; + public HTSManager(final Context context) { + super(context); } @Override - public void disconnect() { - DebugLogger.d(TAG, "Disconnecting device"); - if (mBluetoothGatt != null) { - mBluetoothGatt.disconnect(); - } + protected BleManagerGattCallback getGattCallback() { + return mGattCallback; } - private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); - final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); - - DebugLogger.d(TAG, "Bond state changed for: " + device.getAddress() + " new state: " + bondState + " previous: " + previousBondState); - - // skip other devices - if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress())) - return; - - if (bondState == BluetoothDevice.BOND_BONDED) { - // We've read Battery Level, now enabling HT indications - if (mHTCharacteristic != null) { - enableHTIndication(); - } - mContext.unregisterReceiver(this); - mCallbacks.onBonded(); - } - } - }; - /** * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc */ - private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { - @Override - public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (newState == BluetoothProfile.STATE_CONNECTED) { - DebugLogger.d(TAG, "Device connected"); - mBluetoothGatt.discoverServices(); - //This will send callback to HTSActivity when device get connected - mCallbacks.onDeviceConnected(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - DebugLogger.d(TAG, "Device disconnected"); - //This will send callback to HTSActivity when device get disconnected - mCallbacks.onDeviceDisconnected(); - } - } else { - mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); - } - } + private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() { @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - List services = gatt.getServices(); - for (BluetoothGattService service : services) { - if (service.getUuid().equals(HT_SERVICE_UUID)) { - mHTCharacteristic = service.getCharacteristic(HT_MEASUREMENT_CHARACTERISTIC_UUID); - } else if (service.getUuid().equals(BATTERY_SERVICE)) { - mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC); - } - } - if (mHTCharacteristic != null) { - mCallbacks.onServicesDiscovered(false); - } else { - mCallbacks.onDeviceNotSupported(); - gatt.disconnect(); - return; - } - enableHTIndication(); - } else { - mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status); - } + protected Queue initGatt(final BluetoothGatt gatt) { + final LinkedList requests = new LinkedList<>(); + requests.push(Request.newEnableIndicationsRequest(mHTCharacteristic)); + return requests; } @Override - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC)) { - int batteryValue = characteristic.getValue()[0]; - mCallbacks.onBatteryValueReceived(batteryValue); - } - } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { - if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) { - DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); - mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); - } - } else { - mCallbacks.onError(ERROR_READ_CHARACTERISTIC, status); + protected boolean isRequiredServiceSupported(final BluetoothGatt gatt) { + final BluetoothGattService service = gatt.getService(HT_SERVICE_UUID); + if (service != null) { + mHTCharacteristic = service.getCharacteristic(HT_MEASUREMENT_CHARACTERISTIC_UUID); } + return mHTCharacteristic != null; } @Override - public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { - if (characteristic.getUuid().equals(HT_MEASUREMENT_CHARACTERISTIC_UUID)) { - try { - double tempValue = decodeTemperature(characteristic.getValue()); - mCallbacks.onHTValueReceived(tempValue); - } catch (Exception e) { - DebugLogger.e(TAG, "invalid temperature value"); - } - } + protected void onDeviceDisconnected() { + mHTCharacteristic = null; } @Override - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - readBatteryLevel(); - } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { - if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE) { - mCallbacks.onBondingRequired(); - - final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - mContext.registerReceiver(mBondingBroadcastReceiver, filter); - } else { - DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); - mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); - } - } else { - DebugLogger.e(TAG, ERROR_WRITE_DESCRIPTOR + " (" + status + ")"); - mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status); + public void onCharacteristicIndicated(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + if (mLogSession != null) + Logger.a(mLogSession, TemperatureMeasurementParser.parse(characteristic)); + + try { + final double tempValue = decodeTemperature(characteristic.getValue()); + mCallbacks.onHTValueReceived(tempValue); + } catch (Exception e) { + DebugLogger.e(TAG, "Invalid temperature value", e); } } }; - public void readBatteryLevel() { - if (mBatteryCharacteristic != null) { - mBluetoothGatt.readCharacteristic(mBatteryCharacteristic); - } else { - DebugLogger.e(TAG, "Battery Level Characteristic is null"); - } - } - - /** - * enable Health Thermometer indication on Health Thermometer Measurement characteristic - */ - private void enableHTIndication() { - mBluetoothGatt.setCharacteristicNotification(mHTCharacteristic, true); - BluetoothGattDescriptor descriptor = mHTCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); - mBluetoothGatt.writeDescriptor(descriptor); - } - /** * This method decode temperature value received from Health Thermometer device First byte {0} of data is flag and first bit of flag shows unit information of temperature. if bit 0 has value 1 * then unit is Fahrenheit and Celsius otherwise Four bytes {1 to 4} after Flag bytes represent the temperature value in IEEE-11073 32-bit Float format @@ -257,6 +119,7 @@ private double decodeTemperature(byte[] data) throws Exception { int mantissa = ((thirdOctet << SHIFT_LEFT_16BITS) | (secondOctet << SHIFT_LEFT_8BITS) | (firstOctet)) & HIDE_MSB_8BITS_OUT_OF_32BITS; mantissa = getTwosComplimentOfNegativeMantissa(mantissa); temperatureValue = (mantissa * Math.pow(10, exponential)); + /* * Conversion of temperature unit from Fahrenheit to Celsius if unit is in Fahrenheit * Celsius = (98.6*Fahrenheit -32) 5/9 @@ -282,19 +145,4 @@ private int getTwosComplimentOfNegativeMantissa(int mantissa) { return mantissa; } } - - @Override - public void closeBluetoothGatt() { - try { - mContext.unregisterReceiver(mBondingBroadcastReceiver); - } catch (Exception e) { - // the receiver must have been not registered or unregistered before - } - if (mBluetoothGatt != null) { - mBluetoothGatt.close(); - mBluetoothGatt = null; - mBatteryCharacteristic = null; - mHTCharacteristic = null; - } - } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java index 430bfc02f..db59290a3 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hts/HTSService.java @@ -22,11 +22,6 @@ package no.nordicsemi.android.nrftoolbox.hts; -import no.nordicsemi.android.log.Logger; -import no.nordicsemi.android.nrftoolbox.FeaturesActivity; -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -34,9 +29,14 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.IBinder; import android.support.v4.content.LocalBroadcastManager; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.FeaturesActivity; +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; + public class HTSService extends BleProfileService implements HTSManagerCallbacks { public static final String BROADCAST_HTS_MEASUREMENT = "no.nordicsemi.android.nrftoolbox.hts.BROADCAST_HTS_MEASUREMENT"; public static final String EXTRA_TEMPERATURE = "no.nordicsemi.android.nrftoolbox.hts.EXTRA_TEMPERATURE"; @@ -48,12 +48,11 @@ public class HTSService extends BleProfileService implements HTSManagerCallbacks private final static int DISCONNECT_REQ = 1; private HTSManager mManager; - private boolean mBinded; private final LocalBinder mBinder = new RSCBinder(); /** - * This local binder is an interface for the binded activity to operate with the HTS sensor + * This local binder is an interface for the bonded activity to operate with the HTS sensor */ public class RSCBinder extends LocalBinder { // empty @@ -66,7 +65,7 @@ protected LocalBinder getBinder() { @Override protected BleManager initializeManager() { - return mManager = HTSManager.getHTSManager(); + return mManager = new HTSManager(this); } @Override @@ -88,28 +87,21 @@ public void onDestroy() { } @Override - public IBinder onBind(final Intent intent) { - mBinded = true; - return super.onBind(intent); - } - - @Override - public void onRebind(final Intent intent) { - mBinded = true; + protected void onRebind() { // when the activity rebinds to the service, remove the notification cancelNotification(); + } - // read the battery level when back in the Activity - if (isConnected()) - mManager.readBatteryLevel(); + @Override + protected void onUnbind() { + // when the activity closes we need to show the notification that user is connected to the sensor + createNotification(R.string.hts_notification_connected_message, 0); } @Override - public boolean onUnbind(final Intent intent) { - mBinded = false; - // when the activity closes we need to show the notification that user is connected to the sensor - createNotifcation(R.string.hts_notification_connected_message, 0); - return super.onUnbind(intent); + protected void onServiceStarted() { + // logger is now available. Assign it to the manager + mManager.setLogger(getLogSession()); } @Override @@ -117,6 +109,11 @@ public void onHTValueReceived(final double value) { final Intent broadcast = new Intent(BROADCAST_HTS_MEASUREMENT); broadcast.putExtra(EXTRA_TEMPERATURE, value); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); + + if (!mBinded) { + // Here we may update the notification to display the current temperature. + // TODO modify the notification here + } } /** @@ -128,7 +125,7 @@ public void onHTValueReceived(final double value) { * @param defaults * signals that will be used to notify the user */ - private void createNotifcation(final int messageResId, final int defaults) { + private void createNotification(final int messageResId, final int defaults) { final Intent parentIntent = new Intent(this, FeaturesActivity.class); parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final Intent targetIntent = new Intent(this, HTSActivity.class); @@ -163,7 +160,7 @@ private void cancelNotification() { private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { - Logger.i(getLogSession(), "[HTS] Disconnect action pressed"); + Logger.i(getLogSession(), "[Notification] Disconnect action pressed"); if (isConnected()) getBinder().disconnect(); else diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/AlertLevelParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/AlertLevelParser.java new file mode 100644 index 000000000..fef2995ab --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/AlertLevelParser.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +public class AlertLevelParser { + /** + * Parses the alert level. + * + * @param characteristic + * @return alert level in human readable format + */ + public static String parse(final BluetoothGattCharacteristic characteristic) { + final int value = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0); + + switch (value) { + case 0: + return "No Alert"; + case 1: + return "Mild Alert"; + case 2: + return "High Alert"; + default: + return "Reserved value (" + value + ")"; + } + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BloodPressureMeasurementParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BloodPressureMeasurementParser.java new file mode 100644 index 000000000..a189fa55e --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BloodPressureMeasurementParser.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +import java.util.Calendar; + +public class BloodPressureMeasurementParser { + public static String parse(final BluetoothGattCharacteristic characteristic) { + final StringBuilder builder = new StringBuilder(); + + // first byte - flags + int offset = 0; + final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset++); + + final int unitType = flags & 0x01; + final boolean timestampPresent = (flags & 0x02) > 0; + final boolean pulseRatePresent = (flags & 0x04) > 0; + final boolean userIdPresent = (flags & 0x08) > 0; + final boolean statusPresent = (flags & 0x10) > 0; + + // following bytes - systolic, diastolic and mean arterial pressure + final float systolic = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); + final float diastolic = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 2); + final float meanArterialPressure = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 4); + final String unit = unitType == 0 ? " mmHg" : " kPa"; + offset += 6; + builder.append("Systolic: ").append(systolic).append(unit); + builder.append("\nDiastolic: ").append(diastolic).append(unit); + builder.append("\nMean AP: ").append(meanArterialPressure).append(unit); + + // parse timestamp if present + if (timestampPresent) { + final Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset)); + calendar.set(Calendar.MONTH, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2)); + calendar.set(Calendar.DAY_OF_MONTH, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 3)); + calendar.set(Calendar.HOUR_OF_DAY, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 4)); + calendar.set(Calendar.MINUTE, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 5)); + calendar.set(Calendar.SECOND, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 6)); + offset += 7; + builder.append(String.format("\nTimestamp: %1$tT %1$te.%1$tm.%1$tY", calendar)); + } + + // parse pulse rate if present + if (pulseRatePresent) { + final float pulseRate = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); + offset += 2; + builder.append("\nPulse: ").append(pulseRate); + } + + if (userIdPresent) { + final int userId = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + offset += 1; + builder.append("\nUser ID: ").append(userId); + } + + if (statusPresent) { + final int status = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + // offset += 2; + if ((status & 0x0001) > 0) + builder.append("\nBody movement detected"); + if ((status & 0x0002) > 0) + builder.append("\nCuff too lose"); + if ((status & 0x0004) > 0) + builder.append("\nIrregular pulse detected"); + if ((status & 0x0018) == 0x0008) + builder.append("\nPulse rate exceeds upper limit"); + if ((status & 0x0018) == 0x0010) + builder.append("\nPulse rate is less than lower limit"); + if ((status & 0x0018) == 0x0018) + builder.append("\nPulse rate range: Reserved for future use "); + if ((status & 0x0020) > 0) + builder.append("\nImproper measurement position"); + } + + return builder.toString(); + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BodySensorLocationParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BodySensorLocationParser.java new file mode 100644 index 000000000..7813f13d6 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BodySensorLocationParser.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +public class BodySensorLocationParser { + + public static String parse(final BluetoothGattCharacteristic characteristic) { + final int value = unsignedByteToInt(characteristic.getValue()[0]); + + switch (value) { + case 6: + return "Foot"; + case 5: + return "Ear Lobe"; + case 4: + return "Hand"; + case 3: + return "Finger"; + case 2: + return "Wrist"; + case 1: + return "Chest"; + case 0: + default: + return "Other"; + } + } + + /** + * Convert a signed byte to an unsigned int. + */ + private static int unsignedByteToInt(byte b) { + return b & 0xFF; + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/CSCMeasurementParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/CSCMeasurementParser.java new file mode 100644 index 000000000..22199f39d --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/CSCMeasurementParser.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +public class CSCMeasurementParser { + private static final byte WHEEL_REV_DATA_PRESENT = 0x01; // 1 bit + private static final byte CRANK_REV_DATA_PRESENT = 0x02; // 1 bit + + public static String parse(final BluetoothGattCharacteristic characteristic) { + int offset = 0; + final int flags = characteristic.getValue()[offset]; // 1 byte + offset += 1; + + final boolean wheelRevPresent = (flags & WHEEL_REV_DATA_PRESENT) > 0; + final boolean crankRevPreset = (flags & CRANK_REV_DATA_PRESENT) > 0; + + int wheelRevolutions = 0; + int lastWheelEventTime = 0; + if (wheelRevPresent) { + wheelRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, offset); + offset += 4; + + lastWheelEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); // 1/1024 s + offset += 2; + } + + int crankRevolutions = 0; + int lastCrankEventTime = 0; + if (crankRevPreset) { + crankRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + offset += 2; + + lastCrankEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + //offset += 2; + } + + final StringBuilder builder = new StringBuilder(); + if (wheelRevPresent) { + builder.append(String.format("Wheel rev: %d,\n", wheelRevolutions)); + builder.append(String.format("Last wheel event time: %d ms,\n", lastWheelEventTime)); + } + if (crankRevPreset) { + builder.append(String.format("Crank rev: %d,\n", crankRevolutions)); + builder.append(String.format("Last crank event time: %d ms,\n", lastCrankEventTime)); + } + builder.setLength(builder.length() - 2); + return builder.toString(); + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/DateTimeParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/DateTimeParser.java new file mode 100644 index 000000000..7c6873347 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/DateTimeParser.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +import java.util.Calendar; +import java.util.Locale; + +public class DateTimeParser { + /** + * Parses the date and time info. + * + * @param characteristic + * @return time in human readable format + */ + public static String parse(final BluetoothGattCharacteristic characteristic) { + return parse(characteristic, 0); + } + + /** + * Parses the date and time info. This data has 7 bytes + * + * @param characteristic + * @param offset + * offset to start reading the time + * @return time in human readable format + */ + /* package */static String parse(final BluetoothGattCharacteristic characteristic, final int offset) { + final int year = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + final int month = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2); + final int day = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 3); + final int hours = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 4); + final int minutes = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 5); + final int seconds = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 6); + + final Calendar calendar = Calendar.getInstance(); + calendar.set(year, month - 1, day, hours, minutes, seconds); + + return String.format(Locale.US, "%1$te %1$tb %1$tY, %1$tH:%1$tM:%1$tS", calendar); + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/GlucoseMeasurementContextParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/GlucoseMeasurementContextParser.java new file mode 100644 index 000000000..3252c9268 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/GlucoseMeasurementContextParser.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +public class GlucoseMeasurementContextParser { + private static final int UNIT_kg = 0; + private static final int UNIT_l = 1; + + public static String parse(final BluetoothGattCharacteristic characteristic) { + final StringBuilder builder = new StringBuilder(); + + int offset = 0; + final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + offset += 1; + + final boolean carbohydratePresent = (flags & 0x01) > 0; + final boolean mealPresent = (flags & 0x02) > 0; + final boolean testerHealthPresent = (flags & 0x04) > 0; + final boolean exercisePresent = (flags & 0x08) > 0; + final boolean medicationPresent = (flags & 0x10) > 0; + final int medicationUnit = (flags & 0x20) > 0 ? UNIT_l : UNIT_kg; + final boolean hbA1cPresent = (flags & 0x40) > 0; + final boolean moreFlagsPresent = (flags & 0x80) > 0; + + final int sequenceNumber = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + offset += 2; + + if (moreFlagsPresent) // not supported yet + offset += 1; + + builder.append("Sequence number: ").append(sequenceNumber); + + if (carbohydratePresent) { + final int carbohydrateId = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + final float carbohydrateUnits = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 1); + builder.append("\nCarbohydrate: ").append(getCarbohydrate(carbohydrateId)).append(" (").append(carbohydrateUnits).append(carbohydrateUnits == UNIT_kg ? "kg" : "l").append(")"); + offset += 3; + } + + if (mealPresent) { + final int meal = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + builder.append("\nMeal: ").append(getMeal(meal)); + offset += 1; + } + + if (testerHealthPresent) { + final int testerHealth = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + final int tester = (testerHealth & 0xF0) >> 4; + final int health = (testerHealth & 0x0F); + builder.append("\nTester: ").append(getTester(tester)); + builder.append("\nHealth: ").append(getHealth(health)); + offset += 1; + } + + if (exercisePresent) { + final int exerciseDuration = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + final int exerciseIntensity = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2); + builder.append("\nExercise duration: ").append(exerciseDuration).append("s (intensity ").append(exerciseIntensity).append("%)"); + offset += 3; + } + + if (medicationPresent) { + final int medicationId = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + final float medicationQuantity = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 1); + builder.append("\nMedication: ").append(getMedicationId(medicationId)).append(" (").append(medicationQuantity).append(medicationUnit == UNIT_kg ? "kg" : "l"); + offset += 3; + } + + if (hbA1cPresent) { + final float HbA1c = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); + builder.append("\nHbA1c: ").append(HbA1c).append("%"); + } + return builder.toString(); + } + + private static String getCarbohydrate(final int id) { + switch (id) { + case 1: + return "Breakfast"; + case 2: + return "Lunch"; + case 3: + return "Dinner"; + case 4: + return "Snack"; + case 5: + return "Drink"; + case 6: + return "Supper"; + case 7: + return "Brunch"; + default: + return "Reserved for future use (" + id + ")"; + } + } + + private static String getMeal(final int id) { + switch (id) { + case 1: + return "Preprandial (before meal)"; + case 2: + return "Postprandial (after meal)"; + case 3: + return "Fasting"; + case 4: + return "Casual (snacks, drinks, etc.)"; + case 5: + return "Bedtime"; + default: + return "Reserved for future use (" + id + ")"; + } + } + + private static String getTester(final int id) { + switch (id) { + case 1: + return "Self"; + case 2: + return "Health Care Professional"; + case 3: + return "Lab test"; + case 4: + return "Casual (snacks, drinks, etc.)"; + case 15: + return "Tester value not available"; + default: + return "Reserved for future use (" + id + ")"; + } + } + + private static String getHealth(final int id) { + switch (id) { + case 1: + return "Minor health issues"; + case 2: + return "Major health issues"; + case 3: + return "During menses"; + case 4: + return "Under stress"; + case 5: + return "No health issues"; + case 15: + return "Health value not available"; + default: + return "Reserved for future use (" + id + ")"; + } + } + + private static String getMedicationId(final int id) { + switch (id) { + case 1: + return "Rapid acting insulin"; + case 2: + return "Short acting insulin"; + case 3: + return "Intermediate acting insulin"; + case 4: + return "Long acting insulin"; + case 5: + return "Pre-mixed insulin"; + default: + return "Reserved for future use (" + id + ")"; + } + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/GlucoseMeasurementParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/GlucoseMeasurementParser.java new file mode 100644 index 000000000..17d7fe9e8 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/GlucoseMeasurementParser.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +public class GlucoseMeasurementParser { + private static final int UNIT_kgpl = 0; + private static final int UNIT_molpl = 1; + + private static final int STATUS_DEVICE_BATTERY_LOW = 0x0001; + private static final int STATUS_SENSOR_MALFUNCTION = 0x0002; + private static final int STATUS_SAMPLE_SIZE_FOR_BLOOD_OR_CONTROL_SOLUTION_INSUFFICIENT = 0x0004; + private static final int STATUS_STRIP_INSERTION_ERROR = 0x0008; + private static final int STATUS_STRIP_TYPE_INCORRECT_FOR_DEVICE = 0x0010; + private static final int STATUS_SENSOR_RESULT_TOO_HIGH = 0x0020; + private static final int STATUS_SENSOR_RESULT_TOO_LOW = 0x0040; + private static final int STATUS_SENSOR_TEMPERATURE_TOO_HIGH = 0x0080; + private static final int STATUS_SENSOR_TEMPERATURE_TOO_LOW = 0x0100; + private static final int STATUS_SENSOR_READ_INTERRUPTED = 0x0200; + private static final int STATUS_GENERAL_DEVICE_FAULT = 0x0400; + private static final int STATUS_TIME_FAULT = 0x0800; + + public static String parse(final BluetoothGattCharacteristic characteristic) { + final StringBuilder builder = new StringBuilder(); + + int offset = 0; + final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + offset += 1; + + final boolean timeOffsetPresent = (flags & 0x01) > 0; + final boolean typeAndLocationPresent = (flags & 0x02) > 0; + final int concentrationUnit = (flags & 0x04) > 0 ? UNIT_molpl : UNIT_kgpl; + final boolean sensorStatusAnnunciationPresent = (flags & 0x08) > 0; + final boolean contextInfoFollows = (flags & 0x10) > 0; + + // create and fill the new record + final int sequenceNumber = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + builder.append("Sequence Number: ").append(sequenceNumber); + offset += 2; + + builder.append("\nBase Time: ").append(DateTimeParser.parse(characteristic, offset)); + offset += 7; + + if (timeOffsetPresent) { + // time offset is ignored in the current release + final int timeOffset = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, offset); + builder.append("\nTime Offset: ").append(timeOffset).append(" min"); + offset += 2; + } + + if (typeAndLocationPresent) { + final float glucoseConcentration = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); + final int typeAndLocation = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2); + final int type = (typeAndLocation & 0xF0) >> 4; // TODO this way or around? + final int sampleLocation = (typeAndLocation & 0x0F); + builder.append("\nGlucose Concentration: ").append(glucoseConcentration).append(concentrationUnit == UNIT_kgpl ? " kg/l" : " mol/l"); + builder.append("\nSample Type: ").append(getType(type)); + builder.append("\nSample Location: ").append(getLocation(sampleLocation)); + offset += 3; + } + + if (sensorStatusAnnunciationPresent) { + final int status = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + builder.append("Status:\n").append(getStatusAnnunciation(status)); + } + + builder.append("\nContext information follows: ").append(contextInfoFollows); + return builder.toString(); + } + + private static String getType(final int type) { + switch (type) { + case 1: + return "Capillary Whole blood"; + case 2: + return "Capillary Plasma"; + case 3: + return "Venous Whole blood"; + case 4: + return "Venous Plasma"; + case 5: + return "Arterial Whole blood"; + case 6: + return "Arterial Plasma"; + case 7: + return "Undetermined Whole blood"; + case 8: + return "Undetermined Plasma"; + case 9: + return "Interstitial Fluid (ISF)"; + case 10: + return "Control Solution"; + default: + return "Reserved for future use (" + type + ")"; + } + } + + private static String getLocation(final int location) { + switch (location) { + case 1: + return "Finger"; + case 2: + return "Alternate Site Test (AST)"; + case 3: + return "Earlobe"; + case 4: + return "Control solution"; + case 15: + return "Value not available"; + default: + return "Reserved for future use (" + location + ")"; + } + } + + private static String getStatusAnnunciation(final int status) { + final StringBuilder builder = new StringBuilder(); + if ((status & STATUS_DEVICE_BATTERY_LOW) > 0) + builder.append("\nDevice battery low at time of measurement"); + if ((status & STATUS_SENSOR_MALFUNCTION) > 0) + builder.append("\nSensor malfunction or faulting at time of measurement"); + if ((status & STATUS_SAMPLE_SIZE_FOR_BLOOD_OR_CONTROL_SOLUTION_INSUFFICIENT) > 0) + builder.append("\nSample size for blood or control solution insufficient at time of measurement"); + if ((status & STATUS_STRIP_INSERTION_ERROR) > 0) + builder.append("\nStrip insertion error"); + if ((status & STATUS_STRIP_TYPE_INCORRECT_FOR_DEVICE) > 0) + builder.append("\nStrip type incorrect for device"); + if ((status & STATUS_SENSOR_RESULT_TOO_HIGH) > 0) + builder.append("\nSensor result higher than the device can process"); + if ((status & STATUS_SENSOR_RESULT_TOO_LOW) > 0) + builder.append("\nSensor result lower than the device can process"); + if ((status & STATUS_SENSOR_TEMPERATURE_TOO_HIGH) > 0) + builder.append("\nSensor temperature too high for valid test/result at time of measurement"); + if ((status & STATUS_SENSOR_TEMPERATURE_TOO_LOW) > 0) + builder.append("\nSensor temperature too low for valid test/result at time of measurement"); + if ((status & STATUS_SENSOR_READ_INTERRUPTED) > 0) + builder.append("\nSensor read interrupted because strip was pulled too soon at time of measurement"); + if ((status & STATUS_GENERAL_DEVICE_FAULT) > 0) + builder.append("\nGeneral device fault has occurred in the sensor"); + if ((status & STATUS_TIME_FAULT) > 0) + builder.append("\nTime fault has occurred in the sensor and time may be inaccurate"); + return builder.toString(); + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/HeartRateMeasurementParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/HeartRateMeasurementParser.java new file mode 100644 index 000000000..00bc5ed00 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/HeartRateMeasurementParser.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +import java.util.ArrayList; +import java.util.List; + +public class HeartRateMeasurementParser { + private static final byte HEART_RATE_VALUE_FORMAT = 0x01; // 1 bit + private static final byte SENSOR_CONTACT_STATUS = 0x06; // 2 bits + private static final byte ENERGY_EXPANDED_STATUS = 0x08; // 1 bit + private static final byte RR_INTERVAL = 0x10; // 1 bit + + public static String parse(final BluetoothGattCharacteristic characteristic) { + int offset = 0; + final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset++); + + /* + * false Heart Rate Value Format is set to UINT8. Units: beats per minute (bpm) + * true Heart Rate Value Format is set to UINT16. Units: beats per minute (bpm) + */ + final boolean value16bit = (flags & HEART_RATE_VALUE_FORMAT) > 0; + + /* + * 0 Sensor Contact feature is not supported in the current connection + * 1 Sensor Contact feature is not supported in the current connection + * 2 Sensor Contact feature is supported, but contact is not detected + * 3 Sensor Contact feature is supported and contact is detected + */ + final int sensorContactStatus = (flags & SENSOR_CONTACT_STATUS) >> 1; + + /* + * false Energy Expended field is not present + * true Energy Expended field is present. Units: kilo Joules + */ + final boolean energyExpandedStatus = (flags & ENERGY_EXPANDED_STATUS) > 0; + + /* + * false RR-Interval values are not present. + * true One or more RR-Interval values are present. Units: 1/1024 seconds + */ + final boolean rrIntervalStatus = (flags & RR_INTERVAL) > 0; + + // heart rate value is 8 or 16 bit long + int heartRateValue = characteristic.getIntValue(value16bit ? BluetoothGattCharacteristic.FORMAT_UINT16 : BluetoothGattCharacteristic.FORMAT_UINT8, offset++); // bits per minute + if (value16bit) + offset++; + + // energy expanded value is present if a flag was set + int energyExpanded = -1; + if (energyExpandedStatus) + energyExpanded = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + offset += 2; + + // RR-interval is set when a flag is set + final List rrIntervals = new ArrayList<>(); + if (rrIntervalStatus) { + for (int o = offset; o < characteristic.getValue().length; o += 2) { + final int units = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, o); + rrIntervals.add(units * 1024.0f / 1000.0f); // RR interval is in [1/1024s] + } + } + + final StringBuilder builder = new StringBuilder(); + builder.append("Heart Rate Measurement: ").append(heartRateValue).append(" bpm"); + switch (sensorContactStatus) { + case 0: + case 1: + builder.append(",\nSensor Contact Not Supported"); + break; + case 2: + builder.append(",\nContact is NOT Detected"); + break; + case 3: + builder.append(",\nContact is Detected"); + break; + } + if (energyExpandedStatus) + builder.append(",\nEnergy Expanded: ").append(energyExpanded).append(" kJ"); + if (rrIntervalStatus) { + builder.append(",\nRR Interval: "); + for (final Float interval : rrIntervals) + builder.append(String.format("%.02f ms, ", interval)); + builder.setLength(builder.length() - 2); // remove the ", " at the end + } + return builder.toString(); + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/IntermediateCuffPressureParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/IntermediateCuffPressureParser.java new file mode 100644 index 000000000..0c4cd500d --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/IntermediateCuffPressureParser.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +import java.util.Calendar; + +public class IntermediateCuffPressureParser { + public static String parse(final BluetoothGattCharacteristic characteristic) { + final StringBuilder builder = new StringBuilder(); + + // first byte - flags + int offset = 0; + final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset++); + + final int unitType = flags & 0x01; + final boolean timestampPresent = (flags & 0x02) > 0; + final boolean pulseRatePresent = (flags & 0x04) > 0; + final boolean userIdPresent = (flags & 0x08) > 0; + final boolean statusPresent = (flags & 0x10) > 0; + + // following bytes - pressure + final float pressure = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); + final String unit = unitType == 0 ? "mmHg" : "kPa"; + offset += 6; + builder.append("Cuff pressure: ").append(pressure).append(unit); + + // parse timestamp if present + if (timestampPresent) { + final Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset)); + calendar.set(Calendar.MONTH, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2)); + calendar.set(Calendar.DAY_OF_MONTH, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 3)); + calendar.set(Calendar.HOUR_OF_DAY, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 4)); + calendar.set(Calendar.MINUTE, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 5)); + calendar.set(Calendar.SECOND, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 6)); + offset += 7; + builder.append(String.format("\nTimestamp: %1$tT %1$te.%1$tm.%1$tY", calendar)); + } + + // parse pulse rate if present + if (pulseRatePresent) { + final float pulseRate = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); + offset += 2; + builder.append("\nPulse: ").append(pulseRate); + } + + if (userIdPresent) { + final int userId = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + offset += 1; + builder.append("\nUser ID: ").append(userId); + } + + if (statusPresent) { + final int status = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + // offset += 2; + if ((status & 0x0001) > 0) + builder.append("\nBody movement detected"); + if ((status & 0x0002) > 0) + builder.append("\nCuff too lose"); + if ((status & 0x0004) > 0) + builder.append("\nIrregular pulse detected"); + if ((status & 0x0018) == 0x0008) + builder.append("\nPulse rate exceeds upper limit"); + if ((status & 0x0018) == 0x0010) + builder.append("\nPulse rate is less than lower limit"); + if ((status & 0x0018) == 0x0018) + builder.append("\nPulse rate range: Reserved for future use "); + if ((status & 0x0020) > 0) + builder.append("\nImproper measurement position"); + } + + return builder.toString(); + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/RSCMeasurementParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/RSCMeasurementParser.java new file mode 100644 index 000000000..a3b5f021d --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/RSCMeasurementParser.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +public class RSCMeasurementParser { + private static final byte INSTANTANEOUS_STRIDE_LENGTH_PRESENT = 0x01; // 1 bit + private static final byte TOTAL_DISTANCE_PRESENT = 0x02; // 1 bit + private static final byte WALKING_OR_RUNNING_STATUS_BITS = 0x04; // 1 bit + + public static String parse(final BluetoothGattCharacteristic characteristic) { + int offset = 0; + final int flags = characteristic.getValue()[offset]; // 1 byte + offset += 1; + + final boolean islmPresent = (flags & INSTANTANEOUS_STRIDE_LENGTH_PRESENT) > 0; + final boolean tdPreset = (flags & TOTAL_DISTANCE_PRESENT) > 0; + final boolean running = (flags & WALKING_OR_RUNNING_STATUS_BITS) > 0; + final boolean walking = !running; + + final float instantaneousSpeed = (float) characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset) / 256.0f; // 1/256 m/s + offset += 2; + + final int instantaneousCadence = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); + offset += 1; + + float instantaneousStrideLength = 0; + if (islmPresent) { + instantaneousStrideLength = (float) characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset) / 100.0f; // 1/100 m + offset += 2; + } + + float totalDistance = 0; + if (tdPreset) { + totalDistance = (float) characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, offset) / 10.0f; + // offset += 4; + } + + final StringBuilder builder = new StringBuilder(); + builder.append(String.format("Speed: %.2f m/s, Candence: %d RPM,\n", instantaneousSpeed, instantaneousCadence)); + if (islmPresent) + builder.append(String.format("Instantaneous Stride Length: %.2f m,\n", instantaneousStrideLength)); + if (tdPreset) + builder.append(String.format("Total Distance: %.1f m,\n", totalDistance)); + if (walking) + builder.append("Status: WALKING"); + else + builder.append("Status: RUNNING"); + return builder.toString(); + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/RecordAccessControlPointParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/RecordAccessControlPointParser.java new file mode 100644 index 000000000..85c1b3c27 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/RecordAccessControlPointParser.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +public class RecordAccessControlPointParser { + private final static int OP_CODE_REPORT_STORED_RECORDS = 1; + private final static int OP_CODE_DELETE_STORED_RECORDS = 2; + private final static int OP_CODE_ABORT_OPERATION = 3; + private final static int OP_CODE_REPORT_NUMBER_OF_RECORDS = 4; + private final static int OP_CODE_NUMBER_OF_STORED_RECORDS_RESPONSE = 5; + private final static int OP_CODE_RESPONSE_CODE = 6; + + private final static int OPERATOR_NULL = 0; + private final static int OPERATOR_ALL_RECORDS = 1; + private final static int OPERATOR_LESS_THEN_OR_EQUAL = 2; + private final static int OPERATOR_GREATER_THEN_OR_EQUAL = 3; + private final static int OPERATOR_WITHING_RANGE = 4; + private final static int OPERATOR_FIRST_RECORD = 5; + private final static int OPERATOR_LAST_RECORD = 6; + + private final static int RESPONSE_SUCCESS = 1; + private final static int RESPONSE_OP_CODE_NOT_SUPPORTED = 2; + private final static int RESPONSE_INVALID_OPERATOR = 3; + private final static int RESPONSE_OPERATOR_NOT_SUPPORTED = 4; + private final static int RESPONSE_INVALID_OPERAND = 5; + private final static int RESPONSE_NO_RECORDS_FOUND = 6; + private final static int RESPONSE_ABORT_UNSUCCESSFUL = 7; + private final static int RESPONSE_PROCEDURE_NOT_COMPLETED = 8; + private final static int RESPONSE_OPERAND_NOT_SUPPORTED = 9; + + public static String parse(final BluetoothGattCharacteristic characteristic) { + final StringBuilder builder = new StringBuilder(); + final int opCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0); + final int operator = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1); + + switch (opCode) { + case OP_CODE_REPORT_STORED_RECORDS: + case OP_CODE_DELETE_STORED_RECORDS: + case OP_CODE_ABORT_OPERATION: + case OP_CODE_REPORT_NUMBER_OF_RECORDS: + builder.append(getOpCode(opCode)).append("\n"); + break; + case OP_CODE_NUMBER_OF_STORED_RECORDS_RESPONSE: { + builder.append(getOpCode(opCode)).append(": "); + final int value = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 2); + builder.append(value).append("\n"); + break; + } + case OP_CODE_RESPONSE_CODE: { + builder.append(getOpCode(opCode)).append(" for "); + final int targetOpCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 2); + builder.append(getOpCode(targetOpCode)).append(": "); + final int status = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 3); + builder.append(getStatus(status)).append("\n"); + break; + } + } + + switch (operator) { + case OPERATOR_ALL_RECORDS: + case OPERATOR_FIRST_RECORD: + case OPERATOR_LAST_RECORD: + builder.append("Operator: ").append(getOperator(operator)).append("\n"); + break; + case OPERATOR_GREATER_THEN_OR_EQUAL: + case OPERATOR_LESS_THEN_OR_EQUAL: { + final int filter = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 2); + final int value = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 3); + builder.append("Operator: ").append(getOperator(operator)).append(" ").append(value).append(" (filter: ").append(filter).append(")\n"); + break; + } + case OPERATOR_WITHING_RANGE: { + final int filter = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 2); + final int value1 = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 3); + final int value2 = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 5); + builder.append("Operator: ").append(getOperator(operator)).append(" ").append(value1).append("-").append(value2).append(" (filter: ").append(filter).append(")\n"); + break; + } + } + if (builder.length() > 0) + builder.setLength(builder.length() - 1); + + return builder.toString(); + } + + private static String getOpCode(final int opCode) { + switch (opCode) { + case OP_CODE_REPORT_STORED_RECORDS: + return "Report stored records"; + case OP_CODE_DELETE_STORED_RECORDS: + return "Delete stored records"; + case OP_CODE_ABORT_OPERATION: + return "Abort operation"; + case OP_CODE_REPORT_NUMBER_OF_RECORDS: + return "Report number of stored records"; + case OP_CODE_NUMBER_OF_STORED_RECORDS_RESPONSE: + return "Number of stored records response"; + case OP_CODE_RESPONSE_CODE: + return "Response Code"; + default: + return "Reserved for future use"; + } + } + + private static String getOperator(final int operator) { + switch (operator) { + case OPERATOR_NULL: + return "Null"; + case OPERATOR_ALL_RECORDS: + return "All records"; + case OPERATOR_LESS_THEN_OR_EQUAL: + return "Less than or equal to"; + case OPERATOR_GREATER_THEN_OR_EQUAL: + return "Greater than or equal to"; + case OPERATOR_WITHING_RANGE: + return "Within range of"; + case OPERATOR_FIRST_RECORD: + return "First record(i.e. oldest record)"; + case OPERATOR_LAST_RECORD: + return "Last record (i.e. most recent record)"; + default: + return "Reserved for future use"; + } + } + + private static String getStatus(final int status) { + switch (status) { + case RESPONSE_SUCCESS: + return "Success"; + case RESPONSE_OP_CODE_NOT_SUPPORTED: + return "Operation not supported"; + case RESPONSE_INVALID_OPERATOR: + return "Invalid operator"; + case RESPONSE_OPERATOR_NOT_SUPPORTED: + return "Operator not supported"; + case RESPONSE_INVALID_OPERAND: + return "Invalid operand"; + case RESPONSE_NO_RECORDS_FOUND: + return "No records found"; + case RESPONSE_ABORT_UNSUCCESSFUL: + return "Abort unsuccessful"; + case RESPONSE_PROCEDURE_NOT_COMPLETED: + return "Procedure not completed"; + case RESPONSE_OPERAND_NOT_SUPPORTED: + return "Operand not supported"; + default: + return "Reserved for future use"; + } + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/TemperatureMeasurementParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/TemperatureMeasurementParser.java new file mode 100644 index 000000000..8d210b344 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/TemperatureMeasurementParser.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +public class TemperatureMeasurementParser { + private static final byte TEMPERATURE_UNIT_FLAG = 0x01; // 1 bit + private static final byte TIMESTAMP_FLAG = 0x02; // 1 bits + private static final byte TEMPERATURE_TYPE_FLAG = 0x04; // 1 bit + + public static String parse(final BluetoothGattCharacteristic characteristic) { + int offset = 0; + final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset++); + + /* + * false Temperature is in Celsius degrees + * true Temperature is in Fahrenheit degrees + */ + final boolean fahrenheit = (flags & TEMPERATURE_UNIT_FLAG) > 0; + + /* + * false No Timestamp in the packet + * true There is a timestamp information + */ + final boolean timestampIncluded = (flags & TIMESTAMP_FLAG) > 0; + + /* + * false Temperature type is not included + * true Temperature type included in the packet + */ + final boolean temperatureTypeIncluded = (flags & TEMPERATURE_TYPE_FLAG) > 0; + + final float tempValue = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_FLOAT, offset); + offset += 4; + + String dateTime = null; + if (timestampIncluded) { + dateTime = DateTimeParser.parse(characteristic, offset); + offset += 7; + } + + String type = null; + if (temperatureTypeIncluded) { + type = TemperatureTypeParser.parse(characteristic, offset); + // offset++; + } + + final StringBuilder builder = new StringBuilder(); + builder.append(String.format("%.02f", tempValue)); + + if (fahrenheit) + builder.append("°F"); + else + builder.append("°C"); + + if (timestampIncluded) + builder.append("\nTime: ").append(dateTime); + if (temperatureTypeIncluded) + builder.append("\nType: ").append(type); + return builder.toString(); + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/TemperatureTypeParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/TemperatureTypeParser.java new file mode 100644 index 000000000..5e2873826 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/TemperatureTypeParser.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package no.nordicsemi.android.nrftoolbox.parser; + +import android.bluetooth.BluetoothGattCharacteristic; + +public class TemperatureTypeParser { + + public static String parse(final BluetoothGattCharacteristic characteristic) { + return parse(characteristic, 0); + } + + /* package */static String parse(final BluetoothGattCharacteristic characteristic, final int offset) { + final int type = characteristic.getValue()[offset]; + + switch (type) { + case 1: + return "Armpit"; + case 2: + return "Body (general)"; + case 3: + return "Ear (usually ear lobe)"; + case 4: + return "Finger"; + case 5: + return "Gastro-intestinal Tract"; + case 6: + return "Mouth"; + case 7: + return "Rectum"; + case 8: + return "Toe"; + case 9: + return "Tympanum (ear drum)"; + default: + return "Unknown"; + } + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManager.java index f2810fd9c..de707b8aa 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManager.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManager.java @@ -21,37 +21,845 @@ */ package no.nordicsemi.android.nrftoolbox.profile; -import android.app.Activity; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; -public interface BleManager { +import java.util.Queue; +import java.util.UUID; + +import no.nordicsemi.android.error.GattError; +import no.nordicsemi.android.log.ILogSession; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; +import no.nordicsemi.android.nrftoolbox.utility.ParserUtils; + +/** + *

The BleManager is responsible for managing the low level communication with a Bluetooth Smart device. Please see profiles implementation for an example of use. + * This base manager has been tested against number of devices and samples from Nordic SDK.

+ *

The manager handles connection events and initializes the device after establishing the connection. + *

    + *
  1. For bonded devices it ensures that the Service Changed indications, if this characteristic is present, are enabled. Android does not enable them by default, + * leaving this to the developers.
  2. + *
  3. The manager tries to read the Battery Level characteristic. No matter the result of this operation (for example the Battery Level characteristic may not have the READ property) + * it tries to enable Battery Level notifications, to get battery updates from the device.
  4. + *
  5. Afterwards, the manager initializes the device using given queue of commands. See {@link BleManagerGattCallback#initGatt(android.bluetooth.BluetoothGatt)} method for more details.
  6. + *
  7. When initialization complete, the {@link BleManagerCallbacks#onDeviceReady()} callback is called.
  8. + *
The manager also is responsible for parsing the Battery Level values and calling {@link BleManagerCallbacks#onBatteryValueReceived(int)} method.

+ *

Events from all profiles are being logged into the nRF Logger application, + * which may be downloaded from Google Play: https://play.google.com/store/apps/details?id=no.nordicsemi.android.log

+ *

The nRF Logger application allows you to see application logs without need to connect it to the computer.

+ * + * @param The profile callbacks type + */ +public abstract class BleManager { + private final static String TAG = "BleManager"; + + // Please see https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/android-5.1.0_r1/stack/include/gatt_api.h for more information (line 107) + private static final int GATT_CONN_L2C_FAILURE = 0x01; + private static final int GATT_CONN_TIMEOUT = 0x08; + private static final int GATT_CONN_TERMINATE_PEER_USER = 0x13; + private static final int GATT_CONN_TERMINATE_LOCAL_HOST = 0x16; + private static final int GATT_CONN_FAIL_ESTABLISH = 0x3E; + private static final int GATT_CONN_LMP_TIMEOUT = 0x22; + private static final int GATT_CONN_CANCEL = 0x0100; + private static final int GATT_ERROR = 0x85; // Device not reachable + + private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); + + private final static UUID BATTERY_SERVICE = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb"); + private final static UUID BATTERY_LEVEL_CHARACTERISTIC = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb"); + + private final static UUID GENERIC_ATTRIBUTE_SERVICE = UUID.fromString("00001801-0000-1000-8000-00805f9b34fb"); + private final static UUID SERVICE_CHANGED_CHARACTERISTIC = UUID.fromString("00002A05-0000-1000-8000-00805f9b34fb"); + + private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change"; + private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services"; + private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information"; + private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor"; + private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic"; + + /** + * The log session or null if nRF Logger is not installed. + */ + protected ILogSession mLogSession; + protected E mCallbacks; + private Handler mHandler; + private BluetoothGatt mBluetoothGatt; + private Context mContext; + private boolean mUserDisconnected; + private boolean mConnected; + + private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); + final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); + + // Skip other devices + if (mBluetoothGatt == null || !device.getAddress().equals(mBluetoothGatt.getDevice().getAddress())) + return; + + DebugLogger.i(TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState); + + switch (bondState) { + case BluetoothDevice.BOND_BONDING: + Logger.v(mLogSession, "Bond state: Bonding..."); + mCallbacks.onBondingRequired(); + break; + case BluetoothDevice.BOND_BONDED: + Logger.i(mLogSession, "Bond state: Bonded"); + mCallbacks.onBonded(); + + // Start initializing again. + // In fact, bonding forces additional, internal service discovery (at least on Nexus devices), so this method may safely be used to start this process again. + Logger.v(mLogSession, "Discovering Services..."); + Logger.d(mLogSession, "gatt.discoverServices()"); + mBluetoothGatt.discoverServices(); + break; + } + } + }; + + public BleManager(final Context context) { + mContext = context; + mHandler = new Handler(); + mUserDisconnected = false; + + // Register bonding broadcast receiver + final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + context.registerReceiver(mBondingBroadcastReceiver, filter); + } + + /** + * Returns the context that the manager was created with. + * + * @return the context + */ + protected Context getContext() { + return mContext; + } + + /** + * This method must return the gatt callback used by the manager. + * This method must not create a new gatt callback each time it is being invoked, but rather return a single object. + * + * @return the gatt callback object + */ + protected abstract BleManagerGattCallback getGattCallback(); + + /** + * Returns whether to directly connect to the remote device (false) or to automatically connect as soon as the remote + * device becomes available (true). + * + * @return autoConnect flag value + */ + protected boolean shouldAutoConnect() { + return false; + } /** * Connects to the Bluetooth Smart device - * - * @param context - * this must be an application context, not the Activity. Call {@link Activity#getApplicationContext()} to get one. - * @param device - * a device to connect to + * + * @param device a device to connect to */ - public void connect(final Context context, final BluetoothDevice device); + public void connect(final BluetoothDevice device) { + if (mConnected) + return; + + if (mBluetoothGatt != null) { + Logger.d(mLogSession, "gatt.close()"); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + + final boolean autoConnect = shouldAutoConnect(); + mUserDisconnected = !autoConnect; // We will receive Linkloss events only when the device is connected with autoConnect=true + Logger.v(mLogSession, "Connecting..."); + Logger.d(mLogSession, "gatt = device.connectGatt(autoConnect = " + autoConnect + ")"); + mBluetoothGatt = device.connectGatt(mContext, autoConnect, getGattCallback()); + } /** * Disconnects from the device. Does nothing if not connected. */ - public void disconnect(); + public void disconnect() { + mUserDisconnected = true; + + if (mConnected && mBluetoothGatt != null) { + Logger.v(mLogSession, "Disconnecting..."); + Logger.d(mLogSession, "gatt.disconnect()"); + mBluetoothGatt.disconnect(); + } + } + + /** + * Closes and releases resources. May be also used to unregister broadcast listeners. + */ + public void close() { + try { + mContext.unregisterReceiver(mBondingBroadcastReceiver); + } catch (Exception e) { + // the receiver must have been not registered or unregistered before + } + if (mBluetoothGatt != null) { + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + mUserDisconnected = false; + } + + /** + * Sets the optional log session. This session will be used to log Bluetooth events. + * The logs may be viewed using the nRF Logger application: https://play.google.com/store/apps/details?id=no.nordicsemi.android.log + * Since nRF Logger Library v2.0 an app may define it's own log provider. Use {@link BleProfileServiceReadyActivity#getLocalAuthorityLogger()} to define local log URI. + * NOTE: nRF Logger must be installed prior to nRF Toolbox as it defines the required permission which is used by nRF Toolbox. + * + * @param session the session, or null if nRF Logger is not installed. + */ + public void setLogger(final ILogSession session) { + mLogSession = session; + } /** * Sets the manager callback listener - * - * @param callbacks - * the callback listener + * + * @param callbacks the callback listener */ - public void setGattCallbacks(E callbacks); + public void setGattCallbacks(E callbacks) { + mCallbacks = callbacks; + } /** - * Closes and releases resources. May be also used to unregister broadcast listeners. + * Returns true if this descriptor is from the Service Changed characteristic. + * + * @param descriptor the descriptor to be checked + * @return true if the descriptor belongs to the Service Changed characteristic + */ + private boolean isServiceChangedCCCD(final BluetoothGattDescriptor descriptor) { + if (descriptor == null) + return false; + + return SERVICE_CHANGED_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid()); + } + + /** + * Returns true if the characteristic is the Battery Level characteristic. + * + * @param characteristic the characteristic to be checked + * @return true if the characteristic is the Battery Level characteristic. + */ + private boolean isBatteryLevelCharacteristic(final BluetoothGattCharacteristic characteristic) { + if (characteristic == null) + return false; + + return BATTERY_LEVEL_CHARACTERISTIC.equals(characteristic.getUuid()); + } + + /** + * Returns true if this descriptor is from the Battery Level characteristic. + * + * @param descriptor the descriptor to be checked + * @return true if the descriptor belongs to the Battery Level characteristic + */ + private boolean isBatteryLevelCCCD(final BluetoothGattDescriptor descriptor) { + if (descriptor == null) + return false; + + return BATTERY_LEVEL_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid()); + } + + /** + * When the device is bonded and has the Generic Attribute service and the Service Changed characteristic this method enables indications on this characteristic. + * In case one of the requirements is not fulfilled this method returns false. + * + * @param gatt the gatt device with services discovered + * @return true when the request has been sent, false when the device is not bonded, does not have the Generic Attribute service, the GA service does not have + * the Service Changed characteristic or this characteristic does not have the CCCD. + */ + private boolean ensureServiceChangedEnabled(final BluetoothGatt gatt) { + if (gatt == null) + return false; + + // The Service Changed indications have sense only on bonded devices + final BluetoothDevice device = gatt.getDevice(); + if (device.getBondState() != BluetoothDevice.BOND_BONDED) + return false; + + final BluetoothGattService gaService = gatt.getService(GENERIC_ATTRIBUTE_SERVICE); + if (gaService == null) + return false; + + final BluetoothGattCharacteristic scCharacteristic = gaService.getCharacteristic(SERVICE_CHANGED_CHARACTERISTIC); + if (scCharacteristic == null) + return false; + + Logger.i(mLogSession, "Service Changed characteristic found on a bonded device"); + return enableIndications(scCharacteristic); + } + + /** + * Enables notifications on given characteristic + * + * @return true is the request has been sent, false if one of the arguments was null or the characteristic does not have the CCCD. + */ + protected final boolean enableNotifications(final BluetoothGattCharacteristic characteristic) { + final BluetoothGatt gatt = mBluetoothGatt; + if (gatt == null || characteristic == null) + return false; + + // Check characteristic property + final int properties = characteristic.getProperties(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0) + return false; + + gatt.setCharacteristicNotification(characteristic, true); + final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); + if (descriptor != null) { + descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + Logger.v(mLogSession, "Enabling notifications for " + characteristic.getUuid()); + Logger.d(mLogSession, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x01-00)"); + return gatt.writeDescriptor(descriptor); + } + return false; + } + + /** + * Enables indications on given characteristic + * + * @return true is the request has been sent, false if one of the arguments was null or the characteristic does not have the CCCD. + */ + protected final boolean enableIndications(final BluetoothGattCharacteristic characteristic) { + final BluetoothGatt gatt = mBluetoothGatt; + if (gatt == null || characteristic == null) + return false; + + // Check characteristic property + final int properties = characteristic.getProperties(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == 0) + return false; + + gatt.setCharacteristicNotification(characteristic, true); + final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); + if (descriptor != null) { + descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); + Logger.v(mLogSession, "Enabling indications for " + characteristic.getUuid()); + Logger.d(mLogSession, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x02-00)"); + return gatt.writeDescriptor(descriptor); + } + return false; + } + + /** + * Sends the read request to the given characteristic. + * + * @param characteristic the characteristic to read + * @return true if request has been sent */ - public void closeBluetoothGatt(); + protected final boolean readCharacteristic(final BluetoothGattCharacteristic characteristic) { + final BluetoothGatt gatt = mBluetoothGatt; + if (gatt == null || characteristic == null) + return false; + + // Check characteristic property + final int properties = characteristic.getProperties(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == 0) + return false; + + Logger.v(mLogSession, "Reading characteristic " + characteristic.getUuid()); + Logger.d(mLogSession, "gatt.readCharacteristic(" + characteristic.getUuid() + ")"); + return gatt.readCharacteristic(characteristic); + } + + /** + * Writes the characteristic value to the given characteristic. + * + * @param characteristic the characteristic to write to + * @return true if request has been sent + */ + protected final boolean writeCharacteristic(final BluetoothGattCharacteristic characteristic) { + final BluetoothGatt gatt = mBluetoothGatt; + if (gatt == null || characteristic == null) + return false; + + // Check characteristic property + final int properties = characteristic.getProperties(); + if ((properties & (BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) == 0) + return false; + + Logger.v(mLogSession, "Writing characteristic " + characteristic.getUuid()); + Logger.d(mLogSession, "gatt.writeCharacteristic(" + characteristic.getUuid() + ")"); + return gatt.writeCharacteristic(characteristic); + } + + /** + * Reads the battery level from the device. + * + * @return true if request has been sent + */ + public final boolean readBatteryLevel() { + final BluetoothGatt gatt = mBluetoothGatt; + if (gatt == null) + return false; + + final BluetoothGattService batteryService = gatt.getService(BATTERY_SERVICE); + if (batteryService == null) + return false; + + final BluetoothGattCharacteristic batteryLevelCharacteristic = batteryService.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC); + if (batteryLevelCharacteristic == null) + return false; + + // Check characteristic property + final int properties = batteryLevelCharacteristic.getProperties(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == 0) { + return setBatteryNotifications(true); + } + + Logger.a(mLogSession, "Reading battery level..."); + return readCharacteristic(batteryLevelCharacteristic); + } + + /** + * This method tries to enable notifications on the Battery Level characteristic. + * + * @param enable true to enable battery notifications, false to disable + * @return true if request has been sent + */ + public boolean setBatteryNotifications(final boolean enable) { + final BluetoothGatt gatt = mBluetoothGatt; + if (gatt == null) { + return false; + } + + final BluetoothGattService batteryService = gatt.getService(BATTERY_SERVICE); + if (batteryService == null) + return false; + + final BluetoothGattCharacteristic batteryLevelCharacteristic = batteryService.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC); + if (batteryLevelCharacteristic == null) + return false; + + // Check characteristic property + final int properties = batteryLevelCharacteristic.getProperties(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0) + return false; + + gatt.setCharacteristicNotification(batteryLevelCharacteristic, enable); + final BluetoothGattDescriptor descriptor = batteryLevelCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); + if (descriptor != null) { + if (enable) { + descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + Logger.a(mLogSession, "Enabling battery level notifications..."); + Logger.v(mLogSession, "Enabling notifications for " + BATTERY_LEVEL_CHARACTERISTIC); + Logger.d(mLogSession, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x01-00)"); + } else { + descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); + Logger.a(mLogSession, "Disabling battery level notifications..."); + Logger.v(mLogSession, "Disabling notifications for " + BATTERY_LEVEL_CHARACTERISTIC); + Logger.d(mLogSession, "gatt.writeDescriptor(" + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID + ", value=0x00-00)"); + } + return gatt.writeDescriptor(descriptor); + } + return false; + } + + protected static final class Request { + private enum Type { + WRITE, + READ, + ENABLE_NOTIFICATIONS, + ENABLE_INDICATIONS + } + + private final Type type; + private final BluetoothGattCharacteristic characteristic; + private final byte[] value; + + private Request(final Type type, final BluetoothGattCharacteristic characteristic) { + this.type = type; + this.characteristic = characteristic; + this.value = null; + } + + private Request(final Type type, final BluetoothGattCharacteristic characteristic, final byte[] value) { + this.type = type; + this.characteristic = characteristic; + this.value = value; + } + + public static Request newReadRequest(final BluetoothGattCharacteristic characteristic) { + return new Request(Type.READ, characteristic); + } + + public static Request newWriteRequest(final BluetoothGattCharacteristic characteristic, final byte[] value) { + return new Request(Type.WRITE, characteristic, value); + } + + public static Request newEnableNotificationsRequest(final BluetoothGattCharacteristic characteristic) { + return new Request(Type.ENABLE_NOTIFICATIONS, characteristic); + } + + public static Request newEnableIndicationsRequest(final BluetoothGattCharacteristic characteristic) { + return new Request(Type.ENABLE_INDICATIONS, characteristic); + } + } + + protected abstract class BleManagerGattCallback extends BluetoothGattCallback { + private Queue mInitQueue; + private boolean mInitInProgress; + + /** + * This method should return true when the gatt device supports the required services. + * + * @param gatt the gatt device with services discovered + * @return true when the device has teh required service + */ + protected abstract boolean isRequiredServiceSupported(final BluetoothGatt gatt); + + /** + * This method should return true when the gatt device supports the optional services. + * The default implementation returns false. + * + * @param gatt the gatt device with services discovered + * @return true when the device has teh optional service + */ + protected boolean isOptionalServiceSupported(final BluetoothGatt gatt) { + return false; + } + + /** + * This method should return a list of requests needed to initialize the profile. + * Enabling Service Change indications for bonded devices and reading the Battery Level value and enabling Battery Level notifications + * is handled before executing this queue. The queue should not have requests that are not available, e.g. should not + * read an optional service when it is not supported by the connected device. + *

This method is called when the services has been discovered and the device is supported (has required service).

+ * + * @param gatt the gatt device with services discovered + * @return the queue of requests + */ + protected abstract Queue initGatt(final BluetoothGatt gatt); + + /** + * Called then the initialization queue is complete. + */ + protected void onDeviceReady() { + mCallbacks.onDeviceReady(); + } + + /** + * This method should nullify all services and characteristics of the device. + */ + protected abstract void onDeviceDisconnected(); + + /** + * Callback reporting the result of a characteristic read operation. + * + * @param gatt GATT client invoked {@link BluetoothGatt#readCharacteristic} + * @param characteristic Characteristic that was read from the associated + * remote device. + */ + protected void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + // do nothing + } + + /** + * Callback indicating the result of a characteristic write operation. + *

+ *

If this callback is invoked while a reliable write transaction is + * in progress, the value of the characteristic represents the value + * reported by the remote device. An application should compare this + * value to the desired value to be written. If the values don't match, + * the application must abort the reliable write transaction. + * + * @param gatt GATT client invoked {@link BluetoothGatt#writeCharacteristic} + * @param characteristic Characteristic that was written to the associated + * remote device. + */ + protected void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + // do nothing + } + + protected void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + // do nothing + } + + protected void onCharacteristicIndicated(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + // do nothing + } + + private void onError(final String message, final int errorCode) { + Logger.e(mLogSession, "Error (0x" + Integer.toHexString(errorCode) + "): " + GattError.parse(errorCode)); + mCallbacks.onError(message, errorCode); + } + + @Override + public final void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { + Logger.v(mLogSession, "[Callback] Connection state changed with status: " + status + " and new state: " + newState + " (" + stateToString(newState) + ")"); + + if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { + // Notify the parent activity/service + Logger.i(mLogSession, "Connected to " + gatt.getDevice().getAddress()); + mConnected = true; + mCallbacks.onDeviceConnected(); + + /* + * The onConnectionStateChange event is triggered just after the Android connects to a device. + * In case of bonded devices, the encryption is reestablished AFTER this callback is called. + * Moreover, when the device has Service Changed indication enabled, and the list of services has changed (e.g. using the DFU), + * the indication is received few milliseconds later, depending on the connection interval. + * When received, Android will start performing a service discovery operation itself, internally. + * + * If the mBluetoothGatt.discoverServices() method would be invoked here, if would returned cached services, + * as the SC indication wouldn't be received yet. + * Therefore we have to postpone the service discovery operation until we are (almost, as there is no such callback) sure, that it had to be handled. + * Our tests has shown that 600 ms is enough. It is important to call it AFTER receiving the SC indication, but not necessarily + * after Android finishes the internal service discovery. + * + * NOTE: This applies only for bonded devices with Service Changed characteristic, but to be sure we will postpone + * service discovery for all devices. + */ + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + // Some proximity tags (e.g. nRF PROXIMITY) initialize bonding automatically when connected. + if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_BONDING) { + Logger.v(mLogSession, "Discovering Services..."); + Logger.d(mLogSession, "gatt.discoverServices()"); + gatt.discoverServices(); + } + } + }, 600); + } else { + if (newState == BluetoothProfile.STATE_DISCONNECTED) { + if (status != BluetoothGatt.GATT_SUCCESS) + Logger.w(mLogSession, "Error: (0x" + Integer.toHexString(status) + "): " + GattError.parseConnectionError(status)); + + onDeviceDisconnected(); + mConnected = false; + if (mUserDisconnected) { + Logger.i(mLogSession, "Disconnected"); + mCallbacks.onDeviceDisconnected(); + close(); + } else { + Logger.w(mLogSession, "Connection lost"); + mCallbacks.onLinklossOccur(); + // We are not closing the connection here as the device should try to reconnect automatically. + // This may be only called when the shouldAutoConnect() method returned true. + } + return; + } + + // TODO Should the disconnect method be called or the connection is still valid? Does this ever happen? + Logger.e(mLogSession, "Error (0x" + Integer.toHexString(status) + "): " + GattError.parseConnectionError(status)); + mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); + } + } + + @Override + public final void onServicesDiscovered(final BluetoothGatt gatt, final int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + Logger.i(mLogSession, "Services Discovered"); + if (isRequiredServiceSupported(gatt)) { + Logger.v(mLogSession, "Primary service found"); + final boolean optionalServicesFound = isOptionalServiceSupported(gatt); + if (optionalServicesFound) + Logger.v(mLogSession, "Secondary service found"); + + // Notify the parent activity + mCallbacks.onServicesDiscovered(optionalServicesFound); + + // Obtain the queue of initialization requests + mInitInProgress = true; + mInitQueue = initGatt(gatt); + + // When the device is bonded and has Service Changed characteristic, the indications must be enabled first. + // In case this method returns true we have to continue in the onDescriptorWrite callback + if (ensureServiceChangedEnabled(gatt)) + return; + + // We have discovered services, let's start by reading the battery level value. If the characteristic is not readable, try to enable notifications. + // If there is no Battery service, proceed with the initialization queue. + if (!readBatteryLevel()) + nextRequest(); + } else { + Logger.w(mLogSession, "Device is not supported"); + mCallbacks.onDeviceNotSupported(); + disconnect(); + } + } else { + DebugLogger.e(TAG, "onServicesDiscovered error " + status); + onError(ERROR_DISCOVERY_SERVICE, status); + } + } + + @Override + public final void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + Logger.i(mLogSession, "Read Response received from " + characteristic.getUuid() + ", value: " + ParserUtils.parse(characteristic)); + + if (isBatteryLevelCharacteristic(characteristic)) { + final int batteryValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0); + Logger.a(mLogSession, "Battery level received: " + batteryValue + "%"); + mCallbacks.onBatteryValueReceived(batteryValue); + + // The Battery Level value has been read. Let's try to enable Battery Level notifications. + // If the Battery Level characteristic does not have the NOTIFY property, proceed with the initialization queue. + if (!setBatteryNotifications(true)) + nextRequest(); + } else { + // The value has been read. Notify the manager and proceed with the initialization queue. + onCharacteristicRead(gatt, characteristic); + nextRequest(); + } + } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { + if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) { + DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); + mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); + } + } else { + DebugLogger.e(TAG, "onCharacteristicRead error " + status); + onError(ERROR_READ_CHARACTERISTIC, status); + } + } + + @Override + public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + Logger.i(mLogSession, "Data written to " + characteristic.getUuid() + ", value: " + ParserUtils.parse(characteristic.getValue())); + // The value has been written. Notify the manager and proceed with the initialization queue. + onCharacteristicWrite(gatt, characteristic); + nextRequest(); + } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { + if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) { + DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); + mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); + } + } else { + DebugLogger.e(TAG, "onCharacteristicRead error " + status); + onError(ERROR_READ_CHARACTERISTIC, status); + } + } + + @Override + public final void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + Logger.i(mLogSession, "Data written to descr. " + descriptor.getUuid() + ", value: " + ParserUtils.parse(descriptor)); + + if (isServiceChangedCCCD(descriptor)) { + Logger.a(mLogSession, "Service Changed notifications enabled"); + if (!readBatteryLevel()) + nextRequest(); + } else if (isBatteryLevelCCCD(descriptor)) { + final byte[] value = descriptor.getValue(); + if (value != null && value.length > 0 && value[0] == 0x01) { + Logger.a(mLogSession, "Battery Level notifications enabled"); + nextRequest(); + } else + Logger.a(mLogSession, "Battery Level notifications disabled"); + } else { + nextRequest(); + } + } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { + if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) { + DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); + mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); + } + } else { + DebugLogger.e(TAG, "onDescriptorWrite error " + status); + onError(ERROR_WRITE_DESCRIPTOR, status); + } + } + + @Override + public final void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + final String data = ParserUtils.parse(characteristic); + + if (isBatteryLevelCharacteristic(characteristic)) { + Logger.i(mLogSession, "Notification received from " + characteristic.getUuid() + ", value: " + data); + final int batteryValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0); + Logger.a(mLogSession, "Battery level received: " + batteryValue + "%"); + mCallbacks.onBatteryValueReceived(batteryValue); + } else { + final BluetoothGattDescriptor cccd = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); + final boolean notifications = cccd == null || cccd.getValue() == null || cccd.getValue().length != 2 || cccd.getValue()[0] == 0x01; + + if (notifications) { + Logger.i(mLogSession, "Notification received from " + characteristic.getUuid() + ", value: " + data); + onCharacteristicNotified(gatt, characteristic); + } else { // indications + Logger.i(mLogSession, "Indication received from " + characteristic.getUuid() + ", value: " + data); + onCharacteristicIndicated(gatt, characteristic); + } + } + } + + /** + * Executes the next initialization request. If the last element from the queue has been executed a {@link #onDeviceReady()} callback is called. + */ + private void nextRequest() { + final Queue requests = mInitQueue; + + // Get the first request from the queue + final Request request = requests.poll(); + + // Are we done? + if (request == null) { + if (mInitInProgress) { + mInitInProgress = false; + onDeviceReady(); + } + return; + } + + switch (request.type) { + case READ: { + readCharacteristic(request.characteristic); + break; + } + case WRITE: { + final BluetoothGattCharacteristic characteristic = request.characteristic; + characteristic.setValue(request.value); + writeCharacteristic(characteristic); + break; + } + case ENABLE_NOTIFICATIONS: { + enableNotifications(request.characteristic); + break; + } + case ENABLE_INDICATIONS: { + enableIndications(request.characteristic); + break; + } + } + } + + /** + * Converts the connection state to String value + * @param state the connection state + * @return state as String + */ + private String stateToString(final int state) { + switch (state) { + case BluetoothProfile.STATE_CONNECTED: + return "CONNECTED"; + case BluetoothProfile.STATE_CONNECTING: + return "CONNECTING"; + case BluetoothProfile.STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + return "DISCONNECTED"; + } + } + } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManagerCallbacks.java index 18082c660..5dbde137e 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManagerCallbacks.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleManagerCallbacks.java @@ -32,6 +32,11 @@ public interface BleManagerCallbacks { */ public void onDeviceConnected(); + /** + * Called when user pressed the DISCONNECT button. + */ + public void onDeviceDisconnecting(); + /** * Called when the device has disconnected (when the callback returned {@link BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} with state DISCONNECTED. */ @@ -53,6 +58,11 @@ public interface BleManagerCallbacks { */ public void onServicesDiscovered(final boolean optionalServicesFound); + /** + * Method called when all initialization requests has been completed. + */ + public void onDeviceReady(); + /** * Called when battery value has been received from the device * diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileActivity.java index 855944d02..0f8de7361 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileActivity.java @@ -21,18 +21,13 @@ */ package no.nordicsemi.android.nrftoolbox.profile; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.AppHelpFragment; -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.view.Menu; @@ -42,6 +37,16 @@ import android.widget.TextView; import android.widget.Toast; +import java.util.UUID; + +import no.nordicsemi.android.log.ILogSession; +import no.nordicsemi.android.log.LocalLogSession; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.AppHelpFragment; +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment; +import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; + public abstract class BleProfileActivity extends ActionBarActivity implements BleManagerCallbacks, ScannerFragment.OnDeviceSelectedListener { private static final String TAG = "BaseProfileActivity"; @@ -54,6 +59,7 @@ public abstract class BleProfileActivity extends ActionBarActivity implements Bl private TextView mDeviceNameView; private TextView mBatteryLevelView; private Button mConnectButton; + private ILogSession mLogSession; private boolean mDeviceConnected = false; private String mDeviceName; @@ -185,10 +191,38 @@ public void onConnectClicked(final View view) { } } + /** + * Returns the title resource id that will be used to create logger session. If 0 is returned (default) logger will not be used. + * + * @return the title resource id + */ + protected int getLoggerProfileTitle() { + return 0; + } + + /** + * This method may return the local log content provider authority if local log sessions are supported. + * + * @return local log session content provider URI + */ + protected Uri getLocalAuthorityLogger() { + return null; + } + @Override public void onDeviceSelected(final BluetoothDevice device, final String name) { + final int titleId = getLoggerProfileTitle(); + if (titleId > 0) { + mLogSession = Logger.newSession(getApplicationContext(), getString(titleId), device.getAddress(), name); + // If nRF Logger is not installed we may want to use local logger + if (mLogSession == null && getLocalAuthorityLogger() != null) { + mLogSession = LocalLogSession.newSession(getApplicationContext(), getLocalAuthorityLogger(), device.getAddress(), name); + } + } + mBleManager.setLogger(mLogSession); mDeviceNameView.setText(mDeviceName = name); - mBleManager.connect(getApplicationContext(), device); + mConnectButton.setText(R.string.action_disconnect); + mBleManager.connect(device); } @Override @@ -207,10 +241,15 @@ public void run() { }); } + @Override + public void onDeviceDisconnecting() { + // do nothing + } + @Override public void onDeviceDisconnected() { mDeviceConnected = false; - mBleManager.closeBluetoothGatt(); + mBleManager.close(); runOnUiThread(new Runnable() { @Override public void run() { @@ -256,11 +295,8 @@ public void onBonded() { @Override public void onError(final String message, final int errorCode) { - DebugLogger.e(TAG, "Error occured: " + message + ", error code: " + errorCode); + DebugLogger.e(TAG, "Error occurred: " + message + ", error code: " + errorCode); showToast(message + " (" + errorCode + ")"); - - // refresh UI when connection failed - onDeviceDisconnected(); } @Override diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileExpandableListActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileExpandableListActivity.java index 8fe2a34b8..c9433866a 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileExpandableListActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileExpandableListActivity.java @@ -27,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; @@ -37,6 +38,9 @@ import java.util.UUID; +import no.nordicsemi.android.log.ILogSession; +import no.nordicsemi.android.log.LocalLogSession; +import no.nordicsemi.android.log.Logger; import no.nordicsemi.android.nrftoolbox.AppHelpFragment; import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.app.ExpandableListActivity; @@ -55,6 +59,7 @@ public abstract class BleProfileExpandableListActivity extends ExpandableListAct private TextView mDeviceNameView; private TextView mBatteryLevelView; private Button mConnectButton; + private ILogSession mLogSession; private boolean mDeviceConnected = false; private String mDeviceName; @@ -184,10 +189,38 @@ public void onConnectClicked(final View view) { } } + /** + * Returns the title resource id that will be used to create logger session. If 0 is returned (default) logger will not be used. + * + * @return the title resource id + */ + protected int getLoggerProfileTitle() { + return 0; + } + + /** + * This method may return the local log content provider authority if local log sessions are supported. + * + * @return local log session content provider URI + */ + protected Uri getLocalAuthorityLogger() { + return null; + } + @Override public void onDeviceSelected(final BluetoothDevice device, final String name) { + final int titleId = getLoggerProfileTitle(); + if (titleId > 0) { + mLogSession = Logger.newSession(getApplicationContext(), getString(titleId), device.getAddress(), name); + // If nRF Logger is not installed we may want to use local logger + if (mLogSession == null && getLocalAuthorityLogger() != null) { + mLogSession = LocalLogSession.newSession(getApplicationContext(), getLocalAuthorityLogger(), device.getAddress(), name); + } + } + mBleManager.setLogger(mLogSession); mDeviceNameView.setText(mDeviceName = name); - mBleManager.connect(getApplicationContext(), device); + mConnectButton.setText(R.string.action_disconnect); + mBleManager.connect(device); } @Override @@ -206,10 +239,15 @@ public void run() { }); } + @Override + public void onDeviceDisconnecting() { + // do nothing + } + @Override public void onDeviceDisconnected() { mDeviceConnected = false; - mBleManager.closeBluetoothGatt(); + mBleManager.close(); runOnUiThread(new Runnable() { @Override public void run() { @@ -233,6 +271,18 @@ public void run() { }); } + @Override + public void onServicesDiscovered(boolean optionalServicesFound) { + // this may notify user or show some views + } + + /** + * Called when the initialization process in completed. + */ + public void onDeviceReady() { + // empty default implementation + } + @Override public void onBatteryValueReceived(final int value) { runOnUiThread(new Runnable() { @@ -255,11 +305,8 @@ public void onBonded() { @Override public void onError(final String message, final int errorCode) { - DebugLogger.e(TAG, "Error occured: " + message + ", error code: " + errorCode); + DebugLogger.e(TAG, "Error occurred: " + message + ", error code: " + errorCode); showToast(message + " (" + errorCode + ")"); - - // refresh UI when connection failed - onDeviceDisconnected(); } @Override diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java index 9cfc3bbcc..26571d227 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileService.java @@ -21,9 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.profile; -import no.nordicsemi.android.log.ILogSession; -import no.nordicsemi.android.log.Logger; -import no.nordicsemi.android.nrftoolbox.R; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -36,12 +33,17 @@ import android.support.v4.content.LocalBroadcastManager; import android.widget.Toast; +import no.nordicsemi.android.log.ILogSession; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.R; + public abstract class BleProfileService extends Service implements BleManagerCallbacks { @SuppressWarnings("unused") private static final String TAG = "BleProfileService"; public static final String BROADCAST_CONNECTION_STATE = "no.nordicsemi.android.nrftoolbox.BROADCAST_CONNECTION_STATE"; public static final String BROADCAST_SERVICES_DISCOVERED = "no.nordicsemi.android.nrftoolbox.BROADCAST_SERVICES_DISCOVERED"; + public static final String BROADCAST_DEVICE_READY = "no.nordicsemi.android.nrftoolbox.DEVICE_READY"; public static final String BROADCAST_BOND_STATE = "no.nordicsemi.android.nrftoolbox.BROADCAST_BOND_STATE"; public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL"; public static final String BROADCAST_ERROR = "no.nordicsemi.android.nrftoolbox.BROADCAST_ERROR"; @@ -68,6 +70,8 @@ public abstract class BleProfileService extends Service implements BleManagerCal private BleManager mBleManager; private Handler mHandler; + protected boolean mBinded; + private boolean mActivityFinished; private boolean mConnected; private String mDeviceAddress; private String mDeviceName; @@ -78,12 +82,14 @@ public class LocalBinder extends Binder { * Disconnects from the sensor. */ public final void disconnect() { + onDeviceDisconnecting(); if (!mConnected) { + mBleManager.close(); onDeviceDisconnected(); return; } - // notify user about changing the state to DISCONNECTING + // Notify user about changing the state to DISCONNECTING final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE); broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTING); LocalBroadcastManager.getInstance(BleProfileService.this).sendBroadcast(broadcast); @@ -91,6 +97,14 @@ public final void disconnect() { mBleManager.disconnect(); } + /** + * Sets whether the binded activity if finishing or not. If true, we will turn off battery level notifications in onUnbind(..) method below. + * @param finishing true if the binded activity is finishing + */ + public void setActivityIsFinishing(final boolean finishing) { + mActivityFinished = finishing; + } + /** * Returns the device address * @@ -129,14 +143,9 @@ protected ILogSession getLogSession() { } } - @Override - public IBinder onBind(final Intent intent) { - return getBinder(); - } - /** * Returns the binder implementation. This must return class implementing the additional manager interface that may be used in the binded activity. - * + * * @return the service binder */ protected LocalBinder getBinder() { @@ -145,11 +154,55 @@ protected LocalBinder getBinder() { } @Override - public boolean onUnbind(Intent intent) { + public IBinder onBind(final Intent intent) { + mBinded = true; + return getBinder(); + } + + @Override + public final void onRebind(final Intent intent) { + mBinded = true; + + if (mActivityFinished) + onRebind(); + + if (mActivityFinished && mConnected) { + mActivityFinished = false; + // This method will read the Battery Level value, if possible and then try to enable battery notifications (if it has NOTIFY property). + // If the Battery Level characteristic has only the NOTIFY property, it will only try to enable notifications. + mBleManager.readBatteryLevel(); + } + } + + /** + * Called when the activity has rebinded to the service after being recreated. This method is not called when the activity was killed and recreated just to change the phone orientation. + */ + protected void onRebind() { + // empty + } + + @Override + public final boolean onUnbind(final Intent intent) { + mBinded = false; + + if (mActivityFinished) + onUnbind(); + + // When we are connected, but the application is not open, we are not really interested in battery level notifications. But we will still be receiving other values, if enabled. + if (mActivityFinished && mConnected) + mBleManager.setBatteryNotifications(false); + // we must allow to rebind to the same service return true; } + /** + * Called when the activity has unbinded from the service before being finished. This method is not called when the activity is killed to be recreated just to change the phone orientation. + */ + protected void onUnbind() { + // empty + } + @SuppressWarnings("unchecked") @Override public void onCreate() { @@ -187,8 +240,7 @@ public int onStartCommand(final Intent intent, final int flags, final int startI mDeviceName = device.getName(); onServiceStarted(); - Logger.v(mLogSession, "Connecting..."); - mBleManager.connect(BleProfileService.this, device); + mBleManager.connect(device); return START_REDELIVER_INTENT; } @@ -204,7 +256,7 @@ public void onDestroy() { super.onDestroy(); // shutdown the manager - mBleManager.closeBluetoothGatt(); + mBleManager.close(); Logger.i(mLogSession, "Service destroyed"); mBleManager = null; mDeviceAddress = null; @@ -215,7 +267,6 @@ public void onDestroy() { @Override public void onDeviceConnected() { - Logger.i(mLogSession, "Connected to " + mDeviceAddress); mConnected = true; final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE); @@ -225,9 +276,13 @@ public void onDeviceConnected() { LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); } + @Override + public void onDeviceDisconnecting() { + // do nothing + } + @Override public void onDeviceDisconnected() { - Logger.i(mLogSession, "Disconnected"); mConnected = false; mDeviceAddress = null; mDeviceName = null; @@ -243,7 +298,6 @@ public void onDeviceDisconnected() { @Override public void onLinklossOccur() { - Logger.w(mLogSession, "Connection lost"); mConnected = false; final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE); @@ -253,11 +307,6 @@ public void onLinklossOccur() { @Override public void onServicesDiscovered(final boolean optionalServicesFound) { - Logger.i(mLogSession, "Services Discovered"); - Logger.v(mLogSession, "Primary service found"); - if (optionalServicesFound) - Logger.v(mLogSession, "Secondary service found"); - final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED); broadcast.putExtra(EXTRA_SERVICE_PRIMARY, true); broadcast.putExtra(EXTRA_SERVICE_SECONDARY, optionalServicesFound); @@ -265,10 +314,13 @@ public void onServicesDiscovered(final boolean optionalServicesFound) { } @Override - public void onDeviceNotSupported() { - Logger.i(mLogSession, "Services Discovered"); - Logger.w(mLogSession, "Device is not supported"); + public void onDeviceReady() { + final Intent broadcast = new Intent(BROADCAST_DEVICE_READY); + LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); + } + @Override + public void onDeviceNotSupported() { final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED); broadcast.putExtra(EXTRA_SERVICE_PRIMARY, false); broadcast.putExtra(EXTRA_SERVICE_SECONDARY, false); @@ -279,8 +331,6 @@ public void onDeviceNotSupported() { @Override public void onBatteryValueReceived(final int value) { - Logger.i(mLogSession, "Battery level received: " + value + "%"); - final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL); broadcast.putExtra(EXTRA_BATTERY_LEVEL, value); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); @@ -288,7 +338,6 @@ public void onBatteryValueReceived(final int value) { @Override public void onBondingRequired() { - Logger.v(mLogSession, "Bond state: Bonding..."); showToast(R.string.bonding); final Intent broadcast = new Intent(BROADCAST_BOND_STATE); @@ -298,7 +347,6 @@ public void onBondingRequired() { @Override public void onBonded() { - Logger.i(mLogSession, "Bond state: Bonded"); showToast(R.string.bonded); final Intent broadcast = new Intent(BROADCAST_BOND_STATE); @@ -308,8 +356,6 @@ public void onBonded() { @Override public void onError(final String message, final int errorCode) { - Logger.e(mLogSession, message + " (" + errorCode + ")"); - final Intent broadcast = new Intent(BROADCAST_ERROR); broadcast.putExtra(EXTRA_ERROR_MESSAGE, message); broadcast.putExtra(EXTRA_ERROR_CODE, errorCode); diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileServiceReadyActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileServiceReadyActivity.java index 6000a21e7..e2c647da8 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileServiceReadyActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/profile/BleProfileServiceReadyActivity.java @@ -68,7 +68,7 @@ *

*/ public abstract class BleProfileServiceReadyActivity extends ActionBarActivity implements - ScannerFragment.OnDeviceSelectedListener { + ScannerFragment.OnDeviceSelectedListener, BleManagerCallbacks { private static final String TAG = "BleProfileServiceReadyActivity"; private static final String DEVICE_NAME = "device_name"; @@ -89,59 +89,73 @@ public abstract class BleProfileServiceReadyActivity 0) + onBatteryValueReceived(value); + break; } - } else if (BleProfileService.BROADCAST_BOND_STATE.equals(action)) { - final int state = intent.getIntExtra(BleProfileService.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); - switch (state) { - case BluetoothDevice.BOND_BONDING: - onBondingRequired(); - break; - case BluetoothDevice.BOND_BONDED: - onBonded(); - break; + case BleProfileService.BROADCAST_ERROR: { + final String message = intent.getStringExtra(BleProfileService.EXTRA_ERROR_MESSAGE); + final int errorCode = intent.getIntExtra(BleProfileService.EXTRA_ERROR_CODE, 0); + onError(message, errorCode); + break; } - } else if (BleProfileService.BROADCAST_BATTERY_LEVEL.equals(action)) { - final int value = intent.getIntExtra(BleProfileService.EXTRA_BATTERY_LEVEL, -1); - if (value > 0) - onBatteryValueReceived(value); - } else if (BleProfileService.BROADCAST_ERROR.equals(action)) { - final String message = intent.getStringExtra(BleProfileService.EXTRA_ERROR_MESSAGE); - final int errorCode = intent.getIntExtra(BleProfileService.EXTRA_ERROR_CODE, 0); - onError(message, errorCode); } } }; @@ -209,7 +223,7 @@ protected void onStart() { super.onStart(); /* - * If the service has not been started before the following lines will not start it. However, if it's running, the Activity will be binded to it and + * If the service has not been started before, the following lines will not start it. However, if it's running, the Activity will be binded to it and * notified via mServiceConnection. */ final Intent service = new Intent(this, getServiceClass()); @@ -217,8 +231,8 @@ protected void onStart() { Logger.d(mLogSession, "Binding to the service..."); // (* - see the comment below) /* - * * - When user exited the UARTActivity while being connected the log session is kept in the service. We may not get it before binding to it so in this - * case this event will not be logged. It will, however, be logged after the orientation changes. + * * - When user exited the UARTActivity while being connected, the log session is kept in the service. We may not get it before binding to it so in this + * case this event will not be logged (mLogSession is null until onServiceConnected(..) is called). It will, however, be logged after the orientation changes. */ } @@ -227,6 +241,11 @@ protected void onStop() { super.onStop(); try { + // We don't want to perform some operations (e.g. disable Battery Level notifications) in the service if we are just rotating the screen. + // However, when the activity is finishing, we may want to disable some device features to reduce the battery consumption. + if (mService != null) + mService.setActivityIsFinishing(isFinishing()); + Logger.d(mLogSession, "Unbinding from the service..."); unbindService(mServiceConnection); mService = null; @@ -251,6 +270,7 @@ private static IntentFilter makeIntentFilter() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BleProfileService.BROADCAST_CONNECTION_STATE); intentFilter.addAction(BleProfileService.BROADCAST_SERVICES_DISCOVERED); + intentFilter.addAction(BleProfileService.BROADCAST_DEVICE_READY); intentFilter.addAction(BleProfileService.BROADCAST_BOND_STATE); intentFilter.addAction(BleProfileService.BROADCAST_BATTERY_LEVEL); intentFilter.addAction(BleProfileService.BROADCAST_ERROR); @@ -282,7 +302,7 @@ private static IntentFilter makeIntentFilter() { * * @return the service binder or null */ - protected BleProfileService.LocalBinder getService() { + protected E getService() { return mService; } @@ -372,6 +392,7 @@ public void onConnectClicked(final View view) { showDeviceScanningDialog(getFilterUUID(), isDiscoverableRequired()); } else { Logger.v(mLogSession, "Disconnecting..."); + onDeviceDisconnecting(); mService.disconnect(); } } else { @@ -436,6 +457,11 @@ public void onDeviceConnected() { mConnectButton.setText(R.string.action_disconnect); } + @Override + public void onDeviceDisconnecting() { + // do nothing + } + /** * Called when the device has disconnected (when the callback returned * {@link BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} with state DISCONNECTED. @@ -478,6 +504,13 @@ public void onLinklossOccur() { */ public abstract void onServicesDiscovered(final boolean optionalServicesFound); + /** + * Called when the initialization process in completed. + */ + public void onDeviceReady() { + // empty default implementation + } + /** * Called when the device has started bonding process */ @@ -517,11 +550,8 @@ public void onBatteryValueReceived(final int value) { * @param errorCode the error code */ public void onError(final String message, final int errorCode) { - DebugLogger.e(TAG, "Error occured: " + message + ", error code: " + errorCode); + DebugLogger.e(TAG, "Error occurred: " + message + ", error code: " + errorCode); showToast(message + " (" + errorCode + ")"); - - // refresh UI when connection failed - onDeviceDisconnected(); } /** diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/LinklossFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/LinklossFragment.java index a374595d5..78a52d878 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/LinklossFragment.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/LinklossFragment.java @@ -21,12 +21,13 @@ */ package no.nordicsemi.android.nrftoolbox.proximity; -import no.nordicsemi.android.nrftoolbox.R; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.os.Bundle; +import no.nordicsemi.android.nrftoolbox.R; + public class LinklossFragment extends DialogFragment { private static final String ARG_NAME = "name"; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityActivity.java index 7bb09ceb9..d87db8943 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityActivity.java @@ -21,12 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.proximity; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; import android.app.FragmentManager; import android.content.SharedPreferences; import android.os.Bundle; @@ -37,12 +31,17 @@ import android.widget.CompoundButton; import android.widget.ImageView; +import java.util.UUID; + +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity; +import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; + public class ProximityActivity extends BleProfileServiceReadyActivity { private static final String TAG = "ProximityActivity"; public static final String PREFS_GATT_SERVER_ENABLED = "prefs_gatt_server_enabled"; - private static final String IMMEDIATE_ALERT_STATUS = "immediate_alert_status"; - private boolean isImmediateAlertOn = false; private Button mFindMeButton; private ImageView mLockImage; @@ -64,41 +63,29 @@ private void setGUI() { mGattServerSwitch.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() { @Override public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { - preferences.edit().putBoolean(PREFS_GATT_SERVER_ENABLED, isChecked).commit(); + preferences.edit().putBoolean(PREFS_GATT_SERVER_ENABLED, isChecked).apply(); } }); } @Override - protected void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(IMMEDIATE_ALERT_STATUS, isImmediateAlertOn); + protected int getLoggerProfileTitle() { + return R.string.proximity_feature_title; } @Override - protected void onRestoreInstanceState(final Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); + protected void onServiceBinded(final ProximityService.ProximityBinder binder) { + mGattServerSwitch.setEnabled(false); - isImmediateAlertOn = savedInstanceState.getBoolean(IMMEDIATE_ALERT_STATUS); - if (isDeviceConnected()) { + if (binder.isConnected()) { showOpenLock(); - if (isImmediateAlertOn) { + if (binder.isImmediateAlertOn()) { showSilentMeOnButton(); } } } - @Override - protected int getLoggerProfileTitle() { - return R.string.proximity_feature_title; - } - - @Override - protected void onServiceBinded(final ProximityService.ProximityBinder binder) { - // you may get the binder instance here - } - @Override protected void onServiceUnbinded() { // you may release the binder instance here @@ -131,14 +118,10 @@ public void onFindMeClicked(final View view) { if (isBLEEnabled()) { if (!isDeviceConnected()) { // do nothing - } else if (!isImmediateAlertOn) { + } else if (getService().toggleImmediateAlert()) { showSilentMeOnButton(); - ((ProximityService.ProximityBinder) getService()).startImmediateAlert(); - isImmediateAlertOn = true; } else { showFindMeOnButton(); - ((ProximityService.ProximityBinder) getService()).stopImmediateAlert(); - isImmediateAlertOn = false; } } else { showBLEDialog(); @@ -147,13 +130,8 @@ public void onFindMeClicked(final View view) { @Override protected void setDefaultUI() { - runOnUiThread(new Runnable() { - @Override - public void run() { - mFindMeButton.setText(R.string.proximity_action_findme); - mLockImage.setImageResource(R.drawable.proximity_lock_closed); - } - }); + mFindMeButton.setText(R.string.proximity_action_findme); + mLockImage.setImageResource(R.drawable.proximity_lock_closed); } @Override @@ -162,10 +140,8 @@ public void onServicesDiscovered(boolean optionalServicesFound) { } @Override - public void onDeviceConnected() { - super.onDeviceConnected(); + public void onDeviceReady() { showOpenLock(); - mGattServerSwitch.setEnabled(false); } @Override @@ -202,7 +178,6 @@ public void onLinklossOccur() { } private void resetForLinkloss() { - isImmediateAlertOn = false; setDefaultUI(); } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManager.java index 381bc1b31..12f38118d 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManager.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManager.java @@ -21,78 +21,56 @@ */ package no.nordicsemi.android.nrftoolbox.proximity; -import java.util.List; -import java.util.UUID; - -import no.nordicsemi.android.log.ILogSession; -import no.nordicsemi.android.log.Logger; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothGattServerCallback; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; -import android.media.AudioManager; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; import android.os.Handler; import android.preference.PreferenceManager; import android.util.Log; -public class ProximityManager implements BleManager { - private final String TAG = "ProximityManager"; +import java.util.LinkedList; +import java.util.Queue; +import java.util.UUID; - private ProximityManagerCallbacks mCallbacks; - private BluetoothGattServer mBluetoothGattServer; - private BluetoothGatt mBluetoothGatt; - private BluetoothDevice mDeviceToConnect; - private Context mContext; - private final Handler mHandler; - private ILogSession mLogSession; - private Ringtone mRingtoneNotification; - private Ringtone mRingtoneAlarm; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.parser.AlertLevelParser; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; +import no.nordicsemi.android.nrftoolbox.utility.ParserUtils; - public final static UUID IMMEIDIATE_ALERT_SERVICE_UUID = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb"); - public final static UUID LINKLOSS_SERVICE_UUID = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb"); +public class ProximityManager extends BleManager { + private final String TAG = "ProximityManager"; + /** Immediate Alert service UUID */ + public final static UUID IMMEDIATE_ALERT_SERVICE_UUID = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb"); + /** Linkloss service UUID */ + public final static UUID LINKLOSS_SERVICE_UUID = UUID.fromString("00001803-0000-1000-8000-00805f9b34fb"); + /** Alert Level characteristic UUID */ private static final UUID ALERT_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A06-0000-1000-8000-00805f9b34fb"); - private final static UUID BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb"); - private final static UUID BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb"); - - private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change"; - private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services"; - private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information"; - private final static String ERROR_WRITE_CHARACTERISTIC = "Error on writing characteristic"; - private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic"; - - private final static int HIGH_ALERT = 2; - private final static int NO_ALERT = 0; - - private BluetoothGattCharacteristic mAlertLevelCharacteristic, mLinklossCharacteristic, mBatteryCharacteristic; + private final static byte[] HIGH_ALERT = { 0x02 }; + private final static byte[] NO_ALERT = { 0x00 }; - private boolean userDisconnectedFlag = false; + private BluetoothGattCharacteristic mAlertLevelCharacteristic, mLinklossCharacteristic; + private BluetoothGattServer mBluetoothGattServer; + private BluetoothDevice mDeviceToConnect; + private Handler mHandler; public ProximityManager(Context context) { - initializeAlarm(context); - + super(context); mHandler = new Handler(); + } - // Register bonding broadcast receiver - final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - context.registerReceiver(mBondingBroadcastReceiver, filter); + @Override + protected boolean shouldAutoConnect() { + return true; } private void openGattServer(Context context, BluetoothManager manager) { @@ -111,10 +89,10 @@ private void addImmediateAlertService() { /* * This method must be called in UI thread. It works fine on Nexus devices but if called from other thread (f.e. from onServiceAdded in gatt server callback) it hangs the app. */ - BluetoothGattCharacteristic alertLevel = new BluetoothGattCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, + final BluetoothGattCharacteristic alertLevel = new BluetoothGattCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_WRITE); - alertLevel.setValue(HIGH_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0); - BluetoothGattService immediateAlertService = new BluetoothGattService(IMMEIDIATE_ALERT_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); + alertLevel.setValue(HIGH_ALERT); + final BluetoothGattService immediateAlertService = new BluetoothGattService(IMMEDIATE_ALERT_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); immediateAlertService.addCharacteristic(alertLevel); mBluetoothGattServer.addService(immediateAlertService); } @@ -123,347 +101,209 @@ private void addLinklossService() { /* * This method must be called in UI thread. It works fine on Nexus devices but if called from other thread (f.e. from onServiceAdded in gatt server callback) it hangs the app. */ - BluetoothGattCharacteristic linklossAlertLevel = new BluetoothGattCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE + final BluetoothGattCharacteristic linklossAlertLevel = new BluetoothGattCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_WRITE); - linklossAlertLevel.setValue(HIGH_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0); - BluetoothGattService linklossService = new BluetoothGattService(LINKLOSS_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); + linklossAlertLevel.setValue(HIGH_ALERT); + final BluetoothGattService linklossService = new BluetoothGattService(LINKLOSS_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); linklossService.addCharacteristic(linklossAlertLevel); mBluetoothGattServer.addService(linklossService); } private final BluetoothGattServerCallback mGattServerCallbacks = new BluetoothGattServerCallback() { + @Override - public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { - DebugLogger.d(TAG, "[Proximity Server] onCharacteristicReadRequest " + device.getName()); + public void onServiceAdded(final int status, final BluetoothGattService service) { + Logger.v(mLogSession, "[Server] Service " + service.getUuid() + " added"); + + mHandler.post(new Runnable() { + @Override + public void run() { + // Adding another service from callback thread fails on Samsung S4 with Android 4.3 + if (IMMEDIATE_ALERT_SERVICE_UUID.equals(service.getUuid())) + addLinklossService(); + else { + Logger.i(mLogSession, "[Proximity Server] Gatt server started"); + ProximityManager.super.connect(mDeviceToConnect); + mDeviceToConnect = null; + } + } + }); } @Override - public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, - byte[] value) { - DebugLogger.d(TAG, "[Proximity Server] onCharacteristicWriteRequest " + device.getName()); - final int receivedValue = value[0]; - if (receivedValue != NO_ALERT) { - Logger.i(mLogSession, "[Proximity Server] Immediate alarm request received: ON"); - playAlarm(); + public void onConnectionStateChange(final BluetoothDevice device, final int status, final int newState) { + if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) { + Logger.i(mLogSession, "[Server] Device with address " + device.getAddress() + " connected"); } else { - Logger.i(mLogSession, "[Proximity Server] Immediate alarm request received: OFF"); - stopAlarm(); + if (newState == BluetoothGatt.STATE_DISCONNECTED) { + Logger.i(mLogSession, "[Server] Device disconnected"); + } else { + Logger.e(mLogSession, "[Server] Connection state changed with error " + status); + } } } @Override - public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { - DebugLogger.d(TAG, "[Proximity Server] onConnectionStateChange " + device.getName() + " status: " + status + " new state: " + newState); + public void onCharacteristicReadRequest(final BluetoothDevice device, final int requestId, final int offset, final BluetoothGattCharacteristic characteristic) { + Logger.i(mLogSession, "[Server] Read request for characteristic " + characteristic.getUuid() + " (requestId = " + requestId + ", offset = " + offset + ")"); + Logger.v(mLogSession, "[Server] Sending response: SUCCESS"); + Logger.d(mLogSession, "[Server] sendResponse(GATT_SUCCESS, " + ParserUtils.parse(characteristic.getValue()) + ")"); + mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue()); } @Override - public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) { - DebugLogger.d(TAG, "[Proximity Server] onDescriptorReadRequest " + device.getName()); + public void onCharacteristicWriteRequest(final BluetoothDevice device, final int requestId, final BluetoothGattCharacteristic characteristic, final boolean preparedWrite, + final boolean responseNeeded, final int offset, final byte[] value) { + Logger.i(mLogSession, "[Server] Write request to characteristic " + characteristic.getUuid() + " (requestId = " + requestId + ", value = " + ParserUtils.parse(value) + ", offset = " + offset + ")"); + characteristic.setValue(value); + + if (value != null && value.length == 1) { // small validation + if (value[0] != NO_ALERT[0]) { + Logger.a(mLogSession, "[Server] Immediate alarm request received: " + AlertLevelParser.parse(characteristic)); + mCallbacks.onAlarmTriggered(); + } else { + Logger.a(mLogSession, "[Server] Immediate alarm request received: OFF"); + mCallbacks.onAlarmStopped(); + } + } + if (responseNeeded) { + Logger.v(mLogSession, "[Server] Sending response: SUCCESS"); + Logger.d(mLogSession, "[Server] sendResponse(GATT_SUCCESS)"); + mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null); + } } @Override - public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { - DebugLogger.d(TAG, "[Proximity Server] onDescriptorWriteRequest " + device.getName()); + public void onDescriptorReadRequest(final BluetoothDevice device, final int requestId, final int offset, final BluetoothGattDescriptor descriptor) { + Logger.i(mLogSession, "[Server] Write request to descriptor " + descriptor.getUuid() + " (requestId = " + requestId + ", offset = " + offset + ")"); + // This method is not supported + Logger.v(mLogSession, "[Server] Sending response: REQUEST_NOT_SUPPORTED"); + Logger.d(mLogSession, "[Server] sendResponse(GATT_REQUEST_NOT_SUPPORTED)"); + mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, null); } @Override - public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) { - DebugLogger.d(TAG, "[Proximity Server] onExecuteWrite " + device.getName()); + public void onDescriptorWriteRequest(final BluetoothDevice device, final int requestId, final BluetoothGattDescriptor descriptor, final boolean preparedWrite, + final boolean responseNeeded, final int offset, final byte[] value) { + Logger.i(mLogSession, "[Server] Write request to descriptor " + descriptor.getUuid() + " (requestId = " + requestId + ", value = " + ParserUtils.parse(value) + ", offset = " + offset + ")"); + // This method is not supported + Logger.v(mLogSession, "[Server] Sending response: REQUEST_NOT_SUPPORTED"); + Logger.d(mLogSession, "[Server] sendResponse(GATT_REQUEST_NOT_SUPPORTED)"); + mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, offset, null); } @Override - public void onServiceAdded(final int status, final BluetoothGattService service) { - DebugLogger.d(TAG, "[Proximity Server] onServiceAdded " + service.getUuid()); - - mHandler.post(new Runnable() { - @Override - public void run() { - // adding another service from callback thread fails on Samsung S4 with Android 4.3 - if (IMMEIDIATE_ALERT_SERVICE_UUID.equals(service.getUuid())) - addLinklossService(); - else { - DebugLogger.d(TAG, "[Proximity Server] Gatt server started"); - Logger.i(mLogSession, "[Proximity Server] Gatt server started"); - if (mBluetoothGatt == null) { - mBluetoothGatt = mDeviceToConnect.connectGatt(mContext, false, mGattCallback); - mDeviceToConnect = null; - } else { - mBluetoothGatt.connect(); - } - } - } - }); + public void onExecuteWrite(final BluetoothDevice device, final int requestId, final boolean execute) { + Logger.i(mLogSession, "[Server] Execute write request (requestId = " + requestId + ")"); + // This method is not supported + Logger.v(mLogSession, "[Server] Sending response: REQUEST_NOT_SUPPORTED"); + Logger.d(mLogSession, "[Server] sendResponse(GATT_REQUEST_NOT_SUPPORTED)"); + mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED, 0, null); } }; - /** - * Callbacks for activity {HTSActivity} that implements HTSManagerCallbacks interface activity use this method to register itself for receiving callbacks - */ @Override - public void setGattCallbacks(ProximityManagerCallbacks callbacks) { - mCallbacks = callbacks; - } + public void connect(final BluetoothDevice device) { + // Should we use the GATT Server? + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); + final boolean useGattServer = preferences.getBoolean(ProximityActivity.PREFS_GATT_SERVER_ENABLED, true); - /** - * Sets the log session that can be used to log events - * - * @param logSession - */ - public void setLogger(ILogSession logSession) { - mLogSession = logSession; - } + if (useGattServer) { + // Save the device that we want to connect to. First we will create a GATT Server + mDeviceToConnect = device; - @Override - public void connect(Context context, BluetoothDevice device) { - mContext = context; - // save the device that we want to connect to - mDeviceToConnect = device; - - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - if (preferences.getBoolean(ProximityActivity.PREFS_GATT_SERVER_ENABLED, true)) { - final BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); + final BluetoothManager bluetoothManager = (BluetoothManager) getContext().getSystemService(Context.BLUETOOTH_SERVICE); try { - DebugLogger.d(TAG, "[Proximity Server] Starting Gatt server..."); - Logger.v(mLogSession, "[Proximity Server] Starting Gatt server..."); - openGattServer(context, bluetoothManager); + DebugLogger.d(TAG, "[Server] Starting Gatt server..."); + Logger.v(mLogSession, "[Server] Starting Gatt server..."); + openGattServer(getContext(), bluetoothManager); addImmediateAlertService(); // the BluetoothGattServerCallback#onServiceAdded callback will proceed further operations } catch (final Exception e) { // On Nexus 4&7 with Android 4.4 (build KRT16S) sometimes creating Gatt Server fails. There is a Null Pointer Exception thrown from addCharacteristic method. - Logger.e(mLogSession, "[Proximity Server] Gatt server failed to start"); + Logger.e(mLogSession, "[Server] Gatt server failed to start"); Log.e(TAG, "Creating Gatt Server failed", e); } } else { - if (mBluetoothGatt == null) { - mBluetoothGatt = mDeviceToConnect.connectGatt(context, false, mGattCallback); - mDeviceToConnect = null; - } else { - mBluetoothGatt.connect(); - } + super.connect(device); } } @Override public void disconnect() { - if (mBluetoothGatt != null) { - userDisconnectedFlag = true; - mBluetoothGatt.disconnect(); - stopAlarm(); - closeGattServer(); - } - } - - private void initializeAlarm(Context context) { - final Uri alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); - mRingtoneAlarm = RingtoneManager.getRingtone(context, alarmUri); - mRingtoneAlarm.setStreamType(AudioManager.STREAM_ALARM); - - final Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); - mRingtoneNotification = RingtoneManager.getRingtone(context, notification); - } - - private void playNotification() { - DebugLogger.d(TAG, "playNotification"); - mRingtoneNotification.play(); + super.disconnect(); + closeGattServer(); } - private void playAlarm() { - DebugLogger.d(TAG, "playAlarm"); - final AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - am.setStreamVolume(AudioManager.STREAM_ALARM, am.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE); - mRingtoneAlarm.play(); - } - - private void stopAlarm() { - DebugLogger.d(TAG, "stopAlarm"); - mRingtoneAlarm.stop(); + @Override + protected BleManagerGattCallback getGattCallback() { + return mGattCallback; } /** * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc */ - private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { - @Override - public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (newState == BluetoothProfile.STATE_CONNECTED) { - DebugLogger.d(TAG, "Device connected"); - mBluetoothGatt.discoverServices(); - //This will send callback to ProximityActivity when device get connected - mCallbacks.onDeviceConnected(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - DebugLogger.d(TAG, "Device disconnected"); - if (userDisconnectedFlag) { - mCallbacks.onDeviceDisconnected(); - userDisconnectedFlag = false; - } else { - playNotification(); - mCallbacks.onLinklossOccur(); - } - } - } else { - mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); - } - } + private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() { @Override - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - final List services = gatt.getServices(); - for (BluetoothGattService service : services) { - if (service.getUuid().equals(IMMEIDIATE_ALERT_SERVICE_UUID)) { - DebugLogger.d(TAG, "Immediate Alert service is found"); - mAlertLevelCharacteristic = service.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID); - } else if (service.getUuid().equals(LINKLOSS_SERVICE_UUID)) { - DebugLogger.d(TAG, "Linkloss service is found"); - mLinklossCharacteristic = service.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID); - } else if (service.getUuid().equals(BATTERY_SERVICE_UUID)) { - DebugLogger.d(TAG, "Battery service is found"); - mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID); - } - } - if (mLinklossCharacteristic == null) { - mCallbacks.onDeviceNotSupported(); - gatt.disconnect(); - } else { - mCallbacks.onServicesDiscovered(mAlertLevelCharacteristic != null); - writeLinklossAlertLevel(HIGH_ALERT); - } - } else { - mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status); - } + protected Queue initGatt(final BluetoothGatt gatt) { + final LinkedList requests = new LinkedList<>(); + requests.push(Request.newWriteRequest(mLinklossCharacteristic, HIGH_ALERT)); + return requests; } @Override - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) { - int batteryValue = characteristic.getValue()[0]; - mCallbacks.onBatteryValueReceived(batteryValue); - } - } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { - if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) { - DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); - mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); - } - } else { - mCallbacks.onError(ERROR_READ_CHARACTERISTIC, status); + protected boolean isRequiredServiceSupported(final BluetoothGatt gatt) { + final BluetoothGattService llService = gatt.getService(LINKLOSS_SERVICE_UUID); + if (llService != null) { + mLinklossCharacteristic = llService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID); } + return mLinklossCharacteristic != null; } @Override - public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (characteristic.getUuid().equals(ALERT_LEVEL_CHARACTERISTIC_UUID)) { - if (mBatteryCharacteristic != null) { - readBatteryLevel(); - } - } - } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { - if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) { - DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); - mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); - } - } else { - mCallbacks.onError(ERROR_WRITE_CHARACTERISTIC, status); + protected boolean isOptionalServiceSupported(final BluetoothGatt gatt) { + final BluetoothGattService iaService = gatt.getService(IMMEDIATE_ALERT_SERVICE_UUID); + if (iaService != null) { + mAlertLevelCharacteristic = iaService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID); } + return mAlertLevelCharacteristic != null; } - }; - private final BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() { @Override - public void onReceive(final Context context, final Intent intent) { - final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); - final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); - - // skip other devices - if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress())) - return; - - DebugLogger.i(TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState); - - if (bondState == BluetoothDevice.BOND_BONDING) { - mCallbacks.onBondingRequired(); - return; - } - if (bondState == BluetoothDevice.BOND_BONDED) { - if (mLinklossCharacteristic != null) { - writeLinklossAlertLevel(HIGH_ALERT); - } - mCallbacks.onBonded(); - } + protected void onDeviceDisconnected() { + mAlertLevelCharacteristic = null; + mLinklossCharacteristic = null; } }; - private void readBatteryLevel() { - if (mBatteryCharacteristic != null) { - DebugLogger.d(TAG, "reading battery characteristic"); - mBluetoothGatt.readCharacteristic(mBatteryCharacteristic); - } else { - DebugLogger.w(TAG, "Battery Level Characteristic is null"); - } - } - - @SuppressWarnings("unused") - private void readLinklossAlertLevel() { - if (mLinklossCharacteristic != null) { - DebugLogger.d(TAG, "reading linkloss alert level characteristic"); - mBluetoothGatt.readCharacteristic(mLinklossCharacteristic); - } else { - DebugLogger.w(TAG, "Linkloss Alert Level Characteristic is null"); - } - } - - private void writeLinklossAlertLevel(int alertLevel) { - if (mLinklossCharacteristic != null) { - DebugLogger.d(TAG, "writing linkloss alert level characteristic"); - mLinklossCharacteristic.setValue(alertLevel, BluetoothGattCharacteristic.FORMAT_UINT8, 0); - mBluetoothGatt.writeCharacteristic(mLinklossCharacteristic); - } else { - DebugLogger.w(TAG, "Linkloss Alert Level Characteristic is not found"); - } - } - public void writeImmediateAlertOn() { + Logger.a(mLogSession, "Immediate alarm request: ON"); if (mAlertLevelCharacteristic != null) { - DebugLogger.d(TAG, "writing Immediate alert characteristic On"); - mAlertLevelCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); - mAlertLevelCharacteristic.setValue(HIGH_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0); - mBluetoothGatt.writeCharacteristic(mAlertLevelCharacteristic); + mAlertLevelCharacteristic.setValue(HIGH_ALERT); + writeCharacteristic(mAlertLevelCharacteristic); } else { DebugLogger.w(TAG, "Immediate Alert Level Characteristic is not found"); } } public void writeImmediateAlertOff() { + Logger.a(mLogSession, "Immediate alarm request: OFF"); if (mAlertLevelCharacteristic != null) { - DebugLogger.d(TAG, "writing Immediate alert characteristic Off"); - mAlertLevelCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); - mAlertLevelCharacteristic.setValue(NO_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0); - mBluetoothGatt.writeCharacteristic(mAlertLevelCharacteristic); + mAlertLevelCharacteristic.setValue(NO_ALERT); + writeCharacteristic(mAlertLevelCharacteristic); } else { DebugLogger.w(TAG, "Immediate Alert Level Characteristic is not found"); } } @Override - public void closeBluetoothGatt() { - try { - mContext.unregisterReceiver(mBondingBroadcastReceiver); - } catch (Exception e) { - // the receiver must have been not registered or unregistered before - } - if (mBluetoothGatt != null) { - mBluetoothGatt.close(); - mBluetoothGatt = null; - } + public void close() { + super.close(); + if (mBluetoothGattServer != null) { mBluetoothGattServer.close(); mBluetoothGattServer = null; } - mCallbacks = null; - mLogSession = null; - mRingtoneAlarm = mRingtoneNotification = null; } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManagerCallbacks.java index 1e0e84e5b..dabe9b114 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManagerCallbacks.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityManagerCallbacks.java @@ -24,5 +24,7 @@ import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks; public interface ProximityManagerCallbacks extends BleManagerCallbacks { - // no additional methods + public void onAlarmTriggered(); + + public void onAlarmStopped(); } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java index b7eec6b7d..58b8e6098 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/proximity/ProximityService.java @@ -21,11 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.proximity; -import no.nordicsemi.android.log.Logger; -import no.nordicsemi.android.nrftoolbox.FeaturesActivity; -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -33,35 +28,53 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.IBinder; +import android.media.AudioManager; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; + +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.FeaturesActivity; +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; public class ProximityService extends BleProfileService implements ProximityManagerCallbacks { @SuppressWarnings("unused") private static final String TAG = "ProximityService"; private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_DISCONNECT"; + private final static String ACTION_FIND_ME = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_FIND_ME"; + private final static String ACTION_SILENT_ME = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_SILENT_ME"; private ProximityManager mProximityManager; - private boolean mBinded; + private Ringtone mRingtoneNotification; + private Ringtone mRingtoneAlarm; + private boolean isImmediateAlertOn = false; private final static int NOTIFICATION_ID = 100; private final static int OPEN_ACTIVITY_REQ = 0; private final static int DISCONNECT_REQ = 1; + private final static int FIND_ME_REQ = 2; + private final static int SILENT_ME_REQ = 3; private final LocalBinder mBinder = new ProximityBinder(); /** - * This local binder is an interface for the binded activity to operate with the proximity sensor + * This local binder is an interface for the bonded activity to operate with the proximity sensor */ public class ProximityBinder extends LocalBinder { - public void startImmediateAlert() { - Logger.i(getLogSession(), "[Proximity] Immediate alarm request: ON"); - mProximityManager.writeImmediateAlertOn(); + public boolean toggleImmediateAlert() { + if (isImmediateAlertOn) { + stopImmediateAlert(); + } else { + startImmediateAlert(); + } + return isImmediateAlertOn; // this value is changed by methods above } - public void stopImmediateAlert() { - Logger.i(getLogSession(), "[Proximity] Immediate alarm request: OFF"); - mProximityManager.writeImmediateAlertOff(); + public boolean isImmediateAlertOn() { + return isImmediateAlertOn; } } @@ -75,43 +88,76 @@ protected BleManager initializeManager() { return mProximityManager = new ProximityManager(this); } + /** + * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification. + */ + private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + Logger.i(getLogSession(), "[Notification] Disconnect action pressed"); + if (isConnected()) + getBinder().disconnect(); + else + stopSelf(); + } + }; + + /** + * This broadcast receiver listens for {@link #ACTION_FIND_ME} that may be fired by pressing Find me action button on the notification. + */ + private final BroadcastReceiver mFindMeActionBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + Logger.i(getLogSession(), "[Notification] Find Me action pressed"); + startImmediateAlert(); + } + }; + + /** + * This broadcast receiver listens for {@link #ACTION_SILENT_ME} that may be fired by pressing Silent Me action button on the notification. + */ + private final BroadcastReceiver mSilentMeActionBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + Logger.i(getLogSession(), "[Notification] Silent Me action pressed"); + stopImmediateAlert(); + } + }; + @Override public void onCreate() { super.onCreate(); - final IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_DISCONNECT); - registerReceiver(mDisconnectActionBroadcastReceiver, filter); + initializeAlarm(); + + registerReceiver(mDisconnectActionBroadcastReceiver, new IntentFilter(ACTION_DISCONNECT)); + registerReceiver(mFindMeActionBroadcastReceiver, new IntentFilter(ACTION_FIND_ME)); + registerReceiver(mSilentMeActionBroadcastReceiver, new IntentFilter(ACTION_SILENT_ME)); } @Override public void onDestroy() { - // when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService cancelNotification(); unregisterReceiver(mDisconnectActionBroadcastReceiver); + unregisterReceiver(mFindMeActionBroadcastReceiver); + unregisterReceiver(mSilentMeActionBroadcastReceiver); super.onDestroy(); } @Override - public IBinder onBind(final Intent intent) { - mBinded = true; - return super.onBind(intent); - } - - @Override - public void onRebind(final Intent intent) { - mBinded = true; + protected void onRebind() { // when the activity rebinds to the service, remove the notification cancelNotification(); } @Override - public boolean onUnbind(final Intent intent) { - mBinded = false; - // when the activity closes we need to show the notification that user is connected to the sensor - createNotifcation(R.string.proximity_notification_connected_message, 0); - return super.onUnbind(intent); + public void onUnbind() { + // when the activity closes we need to show the notification that user is connected to the sensor + if (isConnected()) + createNotification(R.string.proximity_notification_connected_message, 0); + else + createNotification(R.string.proximity_notification_linkloss_alert, 0); } @Override @@ -120,16 +166,39 @@ protected void onServiceStarted() { mProximityManager.setLogger(getLogSession()); } + @Override + public void onDeviceDisconnecting() { + stopAlarm(); + } + + @Override + public void onDeviceDisconnected() { + super.onDeviceDisconnected(); + isImmediateAlertOn = false; + } + @Override public void onLinklossOccur() { super.onLinklossOccur(); + isImmediateAlertOn = false; if (!mBinded) { - // when the activity closes we need to show the notification that user is connected to the sensor - createNotifcation(R.string.proximity_notification_linkloss_alert, Notification.DEFAULT_ALL); + // when the activity closes we need to show the notification that user is connected to the sensor + playNotification(); + createNotification(R.string.proximity_notification_linkloss_alert, Notification.DEFAULT_ALL); } } + @Override + public void onAlarmTriggered() { + playAlarm(); + } + + @Override + public void onAlarmStopped() { + stopAlarm(); + } + /** * Creates the notification * @@ -139,7 +208,7 @@ public void onLinklossOccur() { * @param defaults * signals that will be used to notify the user */ - private void createNotifcation(final int messageResId, final int defaults) { + private void createNotification(final int messageResId, final int defaults) { final Intent parentIntent = new Intent(this, FeaturesActivity.class); parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final Intent targetIntent = new Intent(this, ProximityActivity.class); @@ -147,6 +216,15 @@ private void createNotifcation(final int messageResId, final int defaults) { final Intent disconnect = new Intent(ACTION_DISCONNECT); final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent secondAction; + if (isImmediateAlertOn) { + final Intent intent = new Intent(ACTION_SILENT_ME); + secondAction = PendingIntent.getBroadcast(this, SILENT_ME_REQ, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } else { + final Intent intent = new Intent(ACTION_FIND_ME); + secondAction = PendingIntent.getBroadcast(this, FIND_ME_REQ, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } + // both activities above have launchMode="singleTask" in the AndoridManifest.xml file, so if the task is already running, it will be resumed final PendingIntent pendingIntent = PendingIntent.getActivities(this, OPEN_ACTIVITY_REQ, new Intent[] { parentIntent, targetIntent }, PendingIntent.FLAG_UPDATE_CURRENT); final Notification.Builder builder = new Notification.Builder(this).setContentIntent(pendingIntent); @@ -154,6 +232,8 @@ private void createNotifcation(final int messageResId, final int defaults) { builder.setSmallIcon(R.drawable.ic_stat_notify_proximity); builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true); builder.addAction(R.drawable.ic_action_bluetooth, getString(R.string.proximity_notification_action_disconnect), disconnectAction); + if (isConnected()) + builder.addAction(R.drawable.ic_stat_notify_proximity, getString(isImmediateAlertOn ? R.string.proximity_action_silentme : R.string.proximity_action_findme), secondAction); final Notification notification = builder.build(); final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); @@ -168,17 +248,43 @@ private void cancelNotification() { nm.cancel(NOTIFICATION_ID); } - /** - * This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification. - */ - private BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - Logger.i(getLogSession(), "[Proximity] Disconnect action pressed"); - if (isConnected()) - getBinder().disconnect(); - else - stopSelf(); + private void initializeAlarm() { + final Uri alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); + mRingtoneAlarm = RingtoneManager.getRingtone(this, alarmUri); + + final Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + mRingtoneNotification = RingtoneManager.getRingtone(this, notification); + } + + private void playNotification() { + mRingtoneNotification.play(); + } + + private void playAlarm() { + final AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + am.setStreamVolume(AudioManager.STREAM_ALARM, am.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE); + mRingtoneAlarm.play(); + } + + private void stopAlarm() { + mRingtoneAlarm.stop(); + } + + private void startImmediateAlert() { + isImmediateAlertOn = true; + mProximityManager.writeImmediateAlertOn(); + + if (!mBinded) { + createNotification(R.string.proximity_notification_connected_message, 0); } - }; + } + + private void stopImmediateAlert() { + isImmediateAlertOn = false; + mProximityManager.writeImmediateAlertOff(); + + if (!mBinded) { + createNotification(R.string.proximity_notification_connected_message, 0); + } + } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCActivity.java index eb9a8675e..b298bc9b8 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCActivity.java @@ -22,13 +22,6 @@ package no.nordicsemi.android.nrftoolbox.rsc; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.rsc.settings.SettingsFragment; -import no.nordicsemi.android.nrftoolbox.rsc.settings.SettingsActivity; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -40,6 +33,14 @@ import android.view.Menu; import android.widget.TextView; +import java.util.UUID; + +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity; +import no.nordicsemi.android.nrftoolbox.rsc.settings.SettingsActivity; +import no.nordicsemi.android.nrftoolbox.rsc.settings.SettingsFragment; + public class RSCActivity extends BleProfileServiceReadyActivity { private TextView mSpeedView; private TextView mSpeedUnitView; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCManager.java index 0b81316ed..30e3c454f 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCManager.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCManager.java @@ -22,165 +22,71 @@ package no.nordicsemi.android.nrftoolbox.rsc; -import java.util.List; -import java.util.UUID; - -import no.nordicsemi.android.log.ILogSession; -import no.nordicsemi.android.log.Logger; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; -import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -public class RSCManager implements BleManager { - private static final String TAG = "RSCManager"; +import java.util.LinkedList; +import java.util.Queue; +import java.util.UUID; - private RSCManagerCallbacks mCallbacks; - private BluetoothGatt mBluetoothGatt; - private Context mContext; - private ILogSession mLogSession; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.parser.RSCMeasurementParser; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +public class RSCManager extends BleManager { private static final byte INSTANTANEOUS_STRIDE_LENGTH_PRESENT = 0x01; // 1 bit private static final byte TOTAL_DISTANCE_PRESENT = 0x02; // 1 bit private static final byte WALKING_OR_RUNNING_STATUS_BITS = 0x04; // 1 bit + /** Running Speed and Cadence Measurement service UUID */ public final static UUID RUNNING_SPEED_AND_CADENCE_SERVICE_UUID = UUID.fromString("00001814-0000-1000-8000-00805f9b34fb"); - /** Running Speed and Cadence Measurement characteristic */ + /** Running Speed and Cadence Measurement characteristic UUID */ private static final UUID RSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A53-0000-1000-8000-00805f9b34fb"); - private final static UUID BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb"); - /** Battery Level characteristic */ - private final static UUID BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb"); - /** Client configuration descriptor that will allow us to enable notifications and indications */ - private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); - - private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change"; - private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services"; - private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information"; - private final static String ERROR_WRITE_CHARACTERISTIC = "Error on writing characteristic"; - private final static String ERROR_READ_CHARACTERISTIC = "Error on reading characteristic"; - - private BluetoothGattCharacteristic mRSCMeasurementCharacteristic, mBatteryCharacteristic; + private BluetoothGattCharacteristic mRSCMeasurementCharacteristic; public RSCManager(final Context context) { - // Register bonding broadcast receiver - final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - context.registerReceiver(mBondingBroadcastReceiver, filter); - } - - @Override - public void setGattCallbacks(final RSCManagerCallbacks callbacks) { - mCallbacks = callbacks; - } - - public void setLogger(final ILogSession session) { - mLogSession = session; + super(context); } @Override - public void connect(final Context context, final BluetoothDevice device) { - mContext = context; - - Logger.i(mLogSession, "[RSC] Gatt server started"); - if (mBluetoothGatt == null) { - mBluetoothGatt = device.connectGatt(mContext, true, mGattCallback); - } else { - mBluetoothGatt.connect(); - } - } - - @Override - public void disconnect() { - if (mBluetoothGatt != null) { - mBluetoothGatt.disconnect(); - } + protected BleManagerGattCallback getGattCallback() { + return mGattCallback; } /** * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc */ - private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { - @Override - public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (newState == BluetoothProfile.STATE_CONNECTED) { - DebugLogger.d(TAG, "Device connected"); - mBluetoothGatt.discoverServices(); - //This will send callback to RSCActivity when device get connected - mCallbacks.onDeviceConnected(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - DebugLogger.d(TAG, "Device disconnected"); + private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() { - // TODO It should check whether the user has requested disconnection or was it link loss. On Samsung S4 the DevKit reconnects itself just after linkloss but the Service is already dead. - mCallbacks.onDeviceDisconnected(); - closeBluetoothGatt(); - } - } else { - mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); - } + @Override + protected Queue initGatt(final BluetoothGatt gatt) { + final LinkedList requests = new LinkedList<>(); + requests.push(Request.newEnableNotificationsRequest(mRSCMeasurementCharacteristic)); + return requests; } @Override - public void onServicesDiscovered(final BluetoothGatt gatt, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - final List services = gatt.getServices(); - for (BluetoothGattService service : services) { - if (service.getUuid().equals(RUNNING_SPEED_AND_CADENCE_SERVICE_UUID)) { - DebugLogger.d(TAG, "Running Speed and Cadence service is found"); - mRSCMeasurementCharacteristic = service.getCharacteristic(RSC_MEASUREMENT_CHARACTERISTIC_UUID); - } else if (service.getUuid().equals(BATTERY_SERVICE_UUID)) { - DebugLogger.d(TAG, "Battery service is found"); - mBatteryCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID); - } - } - if (mRSCMeasurementCharacteristic == null) { - mCallbacks.onDeviceNotSupported(); - gatt.disconnect(); - } else { - mCallbacks.onServicesDiscovered(false /* more characteristics not supported */); - - // We have discovered services, let's start notifications and indications, one by one: battery, icp (if exists), bpm - if (mBatteryCharacteristic != null) { - readBatteryLevel(gatt); - } else { - enableRSCMeasurementNotification(gatt); - } - } - } else { - mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status); + public boolean isRequiredServiceSupported(final BluetoothGatt gatt) { + final BluetoothGattService service = gatt.getService(RUNNING_SPEED_AND_CADENCE_SERVICE_UUID); + if (service != null) { + mRSCMeasurementCharacteristic = service.getCharacteristic(RSC_MEASUREMENT_CHARACTERISTIC_UUID); } + return mRSCMeasurementCharacteristic != null; } @Override - public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) { - int batteryValue = characteristic.getValue()[0]; - mCallbacks.onBatteryValueReceived(batteryValue); - - enableRSCMeasurementNotification(gatt); - } - } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { - if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) { - DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED); - mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status); - } - } else { - mCallbacks.onError(ERROR_READ_CHARACTERISTIC, status); - } + protected void onDeviceDisconnected() { + mRSCMeasurementCharacteristic = null; } @Override - public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + public void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + if (mLogSession != null) + Logger.a(mLogSession, RSCMeasurementParser.parse(characteristic)); + // Decode the new data int offset = 0; final int flags = characteristic.getValue()[offset]; // 1 byte @@ -213,67 +119,4 @@ public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGat : RSCManagerCallbacks.ACTIVITY_WALKING); } }; - - private BroadcastReceiver mBondingBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); - final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); - - // skip other devices - if (!device.getAddress().equals(mBluetoothGatt.getDevice().getAddress())) - return; - - DebugLogger.i(TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState); - - if (bondState == BluetoothDevice.BOND_BONDING) { - mCallbacks.onBondingRequired(); - return; - } - if (bondState == BluetoothDevice.BOND_BONDED) { - mCallbacks.onBonded(); - } - } - }; - - public void readBatteryLevel() { - readBatteryLevel(mBluetoothGatt); - } - - private void readBatteryLevel(final BluetoothGatt gatt) { - if (mBatteryCharacteristic != null) { - DebugLogger.d(TAG, "reading battery characteristic"); - gatt.readCharacteristic(mBatteryCharacteristic); - } else { - DebugLogger.w(TAG, "Battery Level Characteristic is null"); - } - } - - /** - * Enabling notification on RSC Measurement Characteristic - */ - private void enableRSCMeasurementNotification(final BluetoothGatt gatt) { - DebugLogger.d(TAG, "enableIntermediateCuffPressureNotification()"); - gatt.setCharacteristicNotification(mRSCMeasurementCharacteristic, true); - final BluetoothGattDescriptor descriptor = mRSCMeasurementCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(descriptor); - } - - @Override - public void closeBluetoothGatt() { - try { - mContext.unregisterReceiver(mBondingBroadcastReceiver); - } catch (Exception e) { - // the receiver must have been not registered or unregistered before - } - if (mBluetoothGatt != null) { - mBluetoothGatt.close(); - mBluetoothGatt = null; - } - mCallbacks = null; - mLogSession = null; - } - } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java index 0153928cd..5e73343a9 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/RSCService.java @@ -22,11 +22,6 @@ package no.nordicsemi.android.nrftoolbox.rsc; -import no.nordicsemi.android.log.Logger; -import no.nordicsemi.android.nrftoolbox.FeaturesActivity; -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -35,9 +30,14 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; -import android.os.IBinder; import android.support.v4.content.LocalBroadcastManager; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.FeaturesActivity; +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; + public class RSCService extends BleProfileService implements RSCManagerCallbacks { private static final String TAG = "RSCService"; @@ -54,7 +54,6 @@ public class RSCService extends BleProfileService implements RSCManagerCallbacks private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.rsc.ACTION_DISCONNECT"; private RSCManager mManager; - private boolean mBinded; /** The last value of a cadence */ private float mCadence; @@ -109,28 +108,15 @@ public void onDestroy() { } @Override - public IBinder onBind(final Intent intent) { - mBinded = true; - return super.onBind(intent); - } - - @Override - public void onRebind(final Intent intent) { - mBinded = true; + protected void onRebind() { // when the activity rebinds to the service, remove the notification cancelNotification(); - - // read the battery level when back in the Activity - if (isConnected()) - mManager.readBatteryLevel(); } @Override - public boolean onUnbind(final Intent intent) { - mBinded = false; - // when the activity closes we need to show the notification that user is connected to the sensor + protected void onUnbind() { + // when the activity closes we need to show the notification that user is connected to the sensor createNotifcation(R.string.rsc_notification_connected_message, 0); - return super.onUnbind(intent); } @Override @@ -184,7 +170,7 @@ public void onMeasurementReceived(final float speed, final int cadence, final fl /** * Creates the notification * - * @param messageResIdthe + * @param messageResId * message resource id. The message must have one String parameter,
* f.e. <string name="name">%s is connected</string> * @param defaults @@ -225,7 +211,7 @@ private void cancelNotification() { private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { - Logger.i(getLogSession(), "[RSC] Disconnect action pressed"); + Logger.i(getLogSession(), "[Notification] Disconnect action pressed"); if (isConnected()) getBinder().disconnect(); else diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/settings/SettingsFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/settings/SettingsFragment.java index 9a0e030b0..dcd4fd4f0 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/settings/SettingsFragment.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/rsc/settings/SettingsFragment.java @@ -22,10 +22,8 @@ package no.nordicsemi.android.nrftoolbox.rsc.settings; -import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceFragment; -import android.preference.PreferenceScreen; import no.nordicsemi.android.nrftoolbox.R; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/DeviceListAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/DeviceListAdapter.java index d4dfd3814..c9079a34e 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/DeviceListAdapter.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/DeviceListAdapter.java @@ -21,9 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.scanner; -import java.util.ArrayList; - -import no.nordicsemi.android.nrftoolbox.R; import android.content.Context; import android.view.LayoutInflater; import android.view.View; @@ -32,6 +29,10 @@ import android.widget.ImageView; import android.widget.TextView; +import java.util.ArrayList; + +import no.nordicsemi.android.nrftoolbox.R; + /** * DeviceListAdapter class is list adapter for showing scanned Devices name, address and RSSI image based on RSSI values. */ diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerFragment.java index 2c0c57add..318e4bea6 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerFragment.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerFragment.java @@ -21,11 +21,6 @@ */ package no.nordicsemi.android.nrftoolbox.scanner; -import java.util.Set; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -44,6 +39,12 @@ import android.widget.Button; import android.widget.ListView; +import java.util.Set; +import java.util.UUID; + +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; + /** * ScannerFragment class scan required BLE devices and shows them in a list. This class scans and filter devices with standard BLE Service UUID and devices with custom BLE Service UUID It contains a * list and a button to scan/cancel. There is a interface {@link OnDeviceSelectedListener} which is implemented by activity in order to receive selected device. The scanning will continue for 5 diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerServiceParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerServiceParser.java index 046e1e299..5d958e9ed 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerServiceParser.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/scanner/ScannerServiceParser.java @@ -21,12 +21,12 @@ */ package no.nordicsemi.android.nrftoolbox.scanner; -import java.io.UnsupportedEncodingException; -import java.util.UUID; - import android.bluetooth.BluetoothDevice; import android.util.Log; +import java.io.UnsupportedEncodingException; +import java.util.UUID; + /** * ScannerServiceParser is responsible to parse scanning data and it check if scanned device has required service in it. */ diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTActivity.java index 02fbac6d9..ddca15a09 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTActivity.java @@ -22,11 +22,6 @@ package no.nordicsemi.android.nrftoolbox.uart; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; @@ -40,6 +35,12 @@ import android.support.v4.widget.SlidingPaneLayout; import android.view.View; +import java.util.UUID; + +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileServiceReadyActivity; + public class UARTActivity extends BleProfileServiceReadyActivity implements UARTControlFragment.ControlFragmentListener, UARTInterface { private final static String SIS_EDIT_MODE = "sis_edit_mode"; @@ -236,5 +237,4 @@ public void onAnimationUpdate(final ValueAnimator animation) { } } } - } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTButtonAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTButtonAdapter.java index 32c7c41f7..ad314ce65 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTButtonAdapter.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTButtonAdapter.java @@ -22,7 +22,6 @@ package no.nordicsemi.android.nrftoolbox.uart; -import no.nordicsemi.android.nrftoolbox.R; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; @@ -32,6 +31,8 @@ import android.widget.BaseAdapter; import android.widget.ImageView; +import no.nordicsemi.android.nrftoolbox.R; + public class UARTButtonAdapter extends BaseAdapter { public final static String PREFS_BUTTON_ENABLED = "prefs_uart_enabled_"; public final static String PREFS_BUTTON_COMMAND = "prefs_uart_command_"; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTControlFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTControlFragment.java index 852ec6ac5..de0567194 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTControlFragment.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTControlFragment.java @@ -22,7 +22,6 @@ package no.nordicsemi.android.nrftoolbox.uart; -import no.nordicsemi.android.nrftoolbox.R; import android.app.Activity; import android.app.Fragment; import android.content.SharedPreferences; @@ -38,6 +37,8 @@ import android.widget.AdapterView; import android.widget.GridView; +import no.nordicsemi.android.nrftoolbox.R; + public class UARTControlFragment extends Fragment implements GridView.OnItemClickListener { private final static String TAG = "UARTControlFragment"; private final static String SIS_EDIT_MODE = "sis_edit_mode"; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTEditDialog.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTEditDialog.java index b509f437f..0f1c810eb 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTEditDialog.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTEditDialog.java @@ -22,7 +22,6 @@ package no.nordicsemi.android.nrftoolbox.uart; -import no.nordicsemi.android.nrftoolbox.R; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -42,6 +41,8 @@ import android.widget.GridView; import android.widget.ImageView; +import no.nordicsemi.android.nrftoolbox.R; + public class UARTEditDialog extends DialogFragment implements View.OnClickListener, GridView.OnItemClickListener { private final static String TAG = "UARTEditDialog"; private final static String ARG_INDEX = "index"; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLocalLogContentProvider.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLocalLogContentProvider.java index ad23e4eb3..c196a361b 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLocalLogContentProvider.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLocalLogContentProvider.java @@ -22,9 +22,10 @@ package no.nordicsemi.android.nrftoolbox.uart; -import no.nordicsemi.android.log.localprovider.LocalLogContentProvider; import android.net.Uri; +import no.nordicsemi.android.log.localprovider.LocalLogContentProvider; + public class UARTLocalLogContentProvider extends LocalLogContentProvider { /** The authority for the contacts provider. */ public final static String AUTHORITY = "no.nordicsemi.android.nrftoolbox.uart.log"; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogAdapter.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogAdapter.java index 701e05327..aa863644a 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogAdapter.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogAdapter.java @@ -22,10 +22,6 @@ package no.nordicsemi.android.nrftoolbox.uart; -import java.util.Calendar; - -import no.nordicsemi.android.log.LogContract.Log.Level; -import no.nordicsemi.android.nrftoolbox.R; import android.content.Context; import android.database.Cursor; import android.graphics.Color; @@ -36,6 +32,11 @@ import android.widget.CursorAdapter; import android.widget.TextView; +import java.util.Calendar; + +import no.nordicsemi.android.log.LogContract.Log.Level; +import no.nordicsemi.android.nrftoolbox.R; + public class UARTLogAdapter extends CursorAdapter { private static final SparseIntArray mColors = new SparseIntArray(); diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogFragment.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogFragment.java index 34e792733..3fa8fccea 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogFragment.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTLogFragment.java @@ -22,10 +22,6 @@ package no.nordicsemi.android.nrftoolbox.uart; -import no.nordicsemi.android.log.ILogSession; -import no.nordicsemi.android.log.LogContract; -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; import android.app.ListFragment; import android.app.LoaderManager.LoaderCallbacks; import android.content.BroadcastReceiver; @@ -52,6 +48,11 @@ import android.widget.ListView; import android.widget.TextView; +import no.nordicsemi.android.log.ILogSession; +import no.nordicsemi.android.log.LogContract; +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; + public class UARTLogFragment extends ListFragment implements LoaderCallbacks { private static final String SIS_LOG_SCROLL_POSITION = "sis_scroll_position"; private static final int LOG_SCROLL_NULL = -1; diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManager.java index 435db4973..623606988 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManager.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTManager.java @@ -22,166 +22,85 @@ package no.nordicsemi.android.nrftoolbox.uart; -import java.util.List; -import java.util.UUID; - -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; -import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothProfile; import android.content.Context; -public class UARTManager implements BleManager { - private final static String TAG = "UARTManager"; +import java.util.LinkedList; +import java.util.Queue; +import java.util.UUID; + +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +public class UARTManager extends BleManager { + /** Nordic UART Service UUID */ private final static UUID UART_SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"); - /** TX characteristic */ + /** TX characteristic UUID */ private final static UUID UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"); - /** RX characteristic */ + /** RX characteristic UUID */ private final static UUID UART_RX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"); - /** Client configuration descriptor that will allow us to enable notifications and indications */ - private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); - - private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change"; - private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services"; - private final static String ERROR_WRITE_DESCRIPTOR = "Error on writing descriptor"; private BluetoothGattCharacteristic mTXCharacteristic, mRXCharacteristic; - private UARTManagerCallbacks mCallbacks; - private BluetoothGatt mBluetoothGatt; - private final Context mContext; - public UARTManager(final Context context) { - mContext = context; - } - - @Override - public void setGattCallbacks(final UARTManagerCallbacks callbacks) { - mCallbacks = callbacks; - } - - @Override - public void connect(final Context context, final BluetoothDevice device) { - if (mBluetoothGatt == null) { - mBluetoothGatt = device.connectGatt(mContext, true, mGattCallback); - } else { - mBluetoothGatt.connect(); - } + super(context); } @Override - public void disconnect() { - if (mBluetoothGatt != null) { - mBluetoothGatt.disconnect(); - } - } - - public void send(final String text) { - if (mTXCharacteristic != null) { - mTXCharacteristic.setValue(text); - mBluetoothGatt.writeCharacteristic(mTXCharacteristic); - } + protected BleManagerGattCallback getGattCallback() { + return mGattCallback; } /** * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc */ - private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { - @Override - public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (newState == BluetoothProfile.STATE_CONNECTED) { - DebugLogger.d(TAG, "Device connected"); - mBluetoothGatt.discoverServices(); - //This will send callback to RSCActivity when device get connected - mCallbacks.onDeviceConnected(); - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - DebugLogger.d(TAG, "Device disconnected"); + private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() { - mCallbacks.onDeviceDisconnected(); - closeBluetoothGatt(); - } - } else { - mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); - } + @Override + protected Queue initGatt(final BluetoothGatt gatt) { + final LinkedList requests = new LinkedList<>(); + requests.push(Request.newEnableNotificationsRequest(mRXCharacteristic)); + return requests; } @Override - public void onServicesDiscovered(final BluetoothGatt gatt, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - final List services = gatt.getServices(); - for (BluetoothGattService service : services) { - if (service.getUuid().equals(UART_SERVICE_UUID)) { - DebugLogger.d(TAG, "UART service is found"); - mTXCharacteristic = service.getCharacteristic(UART_TX_CHARACTERISTIC_UUID); - mRXCharacteristic = service.getCharacteristic(UART_RX_CHARACTERISTIC_UUID); - } - } - if (mTXCharacteristic == null || mRXCharacteristic == null) { - mCallbacks.onDeviceNotSupported(); - gatt.disconnect(); - } else { - mCallbacks.onServicesDiscovered(false /* more characteristics not supported */); - - // We have discovered services, let's start notifications on RX characteristics - enableRXNotification(gatt); - } - } else { - mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status); + public boolean isRequiredServiceSupported(final BluetoothGatt gatt) { + final BluetoothGattService service = gatt.getService(UART_SERVICE_UUID); + if (service != null) { + mTXCharacteristic = service.getCharacteristic(UART_TX_CHARACTERISTIC_UUID); + mRXCharacteristic = service.getCharacteristic(UART_RX_CHARACTERISTIC_UUID); } + return mTXCharacteristic != null && mRXCharacteristic != null; } @Override - public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - // do nothing - } else { - mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status); - } + protected void onDeviceDisconnected() { + mTXCharacteristic = null; + mRXCharacteristic = null; } @Override - public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { - if (status == BluetoothGatt.GATT_SUCCESS) { - final String data = characteristic.getStringValue(0); - mCallbacks.onDataSent(data); - } else { - mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status); - } + public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + final String data = characteristic.getStringValue(0); + mCallbacks.onDataSent(data); } @Override - public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { + public void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { final String data = characteristic.getStringValue(0); mCallbacks.onDataReceived(data); } }; /** - * Enabling notification on RX Characteristic + * Sends the given text to TH characteristic. + * @param text the text to be sent */ - private void enableRXNotification(final BluetoothGatt gatt) { - gatt.setCharacteristicNotification(mRXCharacteristic, true); - final BluetoothGattDescriptor descriptor = mRXCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); - descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(descriptor); - } - - @Override - public void closeBluetoothGatt() { - if (mBluetoothGatt != null) { - mBluetoothGatt.close(); - mBluetoothGatt = null; - mTXCharacteristic = null; - mRXCharacteristic = null; + public void send(final String text) { + if (mTXCharacteristic != null) { + mTXCharacteristic.setValue(text); + writeCharacteristic(mTXCharacteristic); } - mCallbacks = null; } - } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java index 4e762666e..1356b2b64 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/uart/UARTService.java @@ -22,12 +22,6 @@ package no.nordicsemi.android.nrftoolbox.uart; -import no.nordicsemi.android.log.ILogSession; -import no.nordicsemi.android.log.Logger; -import no.nordicsemi.android.nrftoolbox.FeaturesActivity; -import no.nordicsemi.android.nrftoolbox.R; -import no.nordicsemi.android.nrftoolbox.profile.BleManager; -import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -35,9 +29,15 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.IBinder; import android.support.v4.content.LocalBroadcastManager; +import no.nordicsemi.android.log.ILogSession; +import no.nordicsemi.android.log.Logger; +import no.nordicsemi.android.nrftoolbox.FeaturesActivity; +import no.nordicsemi.android.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.profile.BleManager; +import no.nordicsemi.android.nrftoolbox.profile.BleProfileService; + public class UARTService extends BleProfileService implements UARTManagerCallbacks { public static final String BROADCAST_UART_TX = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_UART_TX"; public static final String BROADCAST_UART_RX = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_UART_RX"; @@ -50,7 +50,6 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac private final static int DISCONNECT_REQ = 97; // random private UARTManager mManager; - private boolean mBinded; private final LocalBinder mBinder = new UARTBinder(); @@ -95,24 +94,21 @@ public void onDestroy() { } @Override - public IBinder onBind(final Intent intent) { - mBinded = true; - return super.onBind(intent); + protected void onRebind() { + // when the activity rebinds to the service, remove the notification + cancelNotification(); } @Override - public void onRebind(final Intent intent) { - mBinded = true; - // when the activity rebinds to the service, remove the notification - cancelNotification(); + protected void onUnbind() { + // when the activity closes we need to show the notification that user is connected to the sensor + createNotification(R.string.uart_notification_connected_message, 0); } @Override - public boolean onUnbind(final Intent intent) { - mBinded = false; - // when the activity closes we need to show the notification that user is connected to the sensor - createNotifcation(R.string.uart_notification_connected_message, 0); - return super.onUnbind(intent); + protected void onServiceStarted() { + // logger is now available. Assign it to the manager + mManager.setLogger(getLogSession()); } @Override @@ -142,7 +138,7 @@ public void onDataSent(String data) { * @param defaults * signals that will be used to notify the user */ - private void createNotifcation(final int messageResId, final int defaults) { + private void createNotification(final int messageResId, final int defaults) { final Intent parentIntent = new Intent(this, FeaturesActivity.class); parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final Intent targetIntent = new Intent(this, UARTActivity.class); @@ -177,7 +173,7 @@ private void cancelNotification() { private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { - Logger.i(getLogSession(), "Disconnect action pressed"); + Logger.i(getLogSession(), "[Notification] Disconnect action pressed"); if (isConnected()) getBinder().disconnect(); else diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/DebugLogger.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/DebugLogger.java index 2c193a8e2..646db2668 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/DebugLogger.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/DebugLogger.java @@ -21,16 +21,17 @@ */ package no.nordicsemi.android.nrftoolbox.utility; -import no.nordicsemi.android.nrftoolbox.BuildConfig; import android.util.Log; +import no.nordicsemi.android.nrftoolbox.BuildConfig; + public class DebugLogger { public static void v(final String tag, final String text) { if (BuildConfig.DEBUG) Log.v(tag, text); } - public static void d(String tag, String text) { + public static void d(final String tag, final String text) { if (BuildConfig.DEBUG) { Log.d(tag, text); } @@ -41,7 +42,7 @@ public static void i(final String tag, final String text) { Log.i(tag, text); } - public static void w(String tag, String text) { + public static void w(final String tag, final String text) { if (BuildConfig.DEBUG) { Log.w(tag, text); } @@ -52,9 +53,20 @@ public static void e(final String tag, final String text) { Log.e(tag, text); } - public static void wtf(String tag, String text) { + public static void e(final String tag, final String text, final Throwable e) { + if (BuildConfig.DEBUG) + Log.e(tag, text, e); + } + + public static void wtf(final String tag, final String text) { if (BuildConfig.DEBUG) { Log.wtf(tag, text); } } + + public static void wtf(final String tag, final String text, final Throwable e) { + if (BuildConfig.DEBUG) { + Log.wtf(tag, text, e); + } + } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/ParserUtils.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/ParserUtils.java new file mode 100644 index 000000000..a4fb266e1 --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/utility/ParserUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015, Nordic Semiconductor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package no.nordicsemi.android.nrftoolbox.utility; + +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; + +public class ParserUtils { + final private static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + + public static String parse(final BluetoothGattCharacteristic characteristic) { + return parse(characteristic.getValue()); + } + + public static String parse(final BluetoothGattDescriptor descriptor) { + return parse(descriptor.getValue()); + } + + public static String parse(final byte[] data) { + if (data == null || data.length == 0) + return ""; + + final char[] out = new char[data.length * 3 - 1]; + for (int j = 0; j < data.length; j++) { + int v = data[j] & 0xFF; + out[j * 3] = HEX_ARRAY[v >>> 4]; + out[j * 3 + 1] = HEX_ARRAY[v & 0x0F]; + if (j != data.length - 1) + out[j * 3 + 2] = '-'; + } + return "(0x) " + new String(out); + } +} diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetBoldTextView.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetBoldTextView.java index 7e15f5333..830b015cf 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetBoldTextView.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetBoldTextView.java @@ -21,12 +21,13 @@ */ package no.nordicsemi.android.nrftoolbox.widget; -import no.nordicsemi.android.nrftoolbox.R; import android.content.Context; import android.graphics.Typeface; import android.util.AttributeSet; import android.widget.TextView; +import no.nordicsemi.android.nrftoolbox.R; + public class TrebuchetBoldTextView extends TextView { public TrebuchetBoldTextView(Context context) { diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetTextView.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetTextView.java index ebbde29ee..0bdeac63f 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetTextView.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/widget/TrebuchetTextView.java @@ -21,12 +21,13 @@ */ package no.nordicsemi.android.nrftoolbox.widget; -import no.nordicsemi.android.nrftoolbox.R; import android.content.Context; import android.graphics.Typeface; import android.util.AttributeSet; import android.widget.TextView; +import no.nordicsemi.android.nrftoolbox.R; + public class TrebuchetTextView extends TextView { public TrebuchetTextView(Context context) { diff --git a/app/src/main/res/menu/csc_menu.xml b/app/src/main/res/menu/csc_menu.xml deleted file mode 100644 index 594e00511..000000000 --- a/app/src/main/res/menu/csc_menu.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/menu/dfu_menu.xml b/app/src/main/res/menu/dfu_menu.xml deleted file mode 100644 index 594e00511..000000000 --- a/app/src/main/res/menu/dfu_menu.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/raw/ble_app_hrs_dfu_s110_v8_0_0.zip b/app/src/main/res/raw/ble_app_hrs_dfu_s110_v8_0_0.zip new file mode 100644 index 0000000000000000000000000000000000000000..dd3ad332828a40dbb1b8b3e528e5386395619f17 GIT binary patch literal 20630 zcmcJ1d3+nyz3-XPHj-sq-V(`{of&zPolOp#11*)MFdo@XNCMm&3Oq(h;Ev2Tc0!O7 z7Rd=gb^waAS-QNQX>br2i3)e#z zuLFB7-P1mXx-UJ?xA|M&&+op}_D2`*T?_i7@BOVmgY>9KQezWDBF(06Glb5^UqOEG z*Z<$w)n-CW%A+4}2~n}Ny!U}=_mO~4WOc6XOQNsY(q>+37R}eEzF}xHtYz1+xHF>X zxzRKD!<{gR@BiBv2Ei(^iY>^>+{JH;6l=Diui5cS|AbVTi2p`hpT>Mo#u(xo$q^Zm z@VtkqvLWh!O}wk>*?Ljgwv4pS1{3A zDqQNuipkTCQG=KheAZ&ntV!-v;Sz4lSbyQPA(<03sD5|IJ=f;+2`0(X=ZdM*z>DX@ zamPrJXcLX1OU$HG8D4aZ2HZ^4?{&#wq<+;5TOo?;9l~HBv7B6SKaTcml!fS);d9&OfV z+mLLVH51GcGBBj{1sT8L02zoVtiQld4zMUYO3Mb=BTP_HJbs6t9B~9$(H3O1`bb~% z4)=#AqS3v9Z=Nv?rvNK4k@fyC@%shB@3v$-zsx)0@3C0(4*Q8W=NH66^0~&{949?X zoX(dchk`l5m&0oFZqA63GN%&|a6zYx75M{6l6OebF=7r8o|7dxA-^IULjKkC-kAFC z1;?nrfbq|c5PmFJxR&ixB21qmu86RyqKri}q_jz_*Gf)fUtkWC0XF5$umgrv{snWA ze&<{uUPdR&Sj{Igsb#Z-S2|_^e;eR-j94VULcA6&)=azxjd%*M&jJ|HkP;>xqiVK| zAr+YOeo5zdvE{ijYab$NPxztxhXGml%%)_h}~S&uzV%p{-HO(BL) z$d1vo8Os=OYZo7s2+UfhAvzbkDuV5#gB=r}- zMX)+NlVna7E+O?(cy1iYZIh#{m(}dq*+s-0!2CU6ht)`G(M01YC&h%g+%8&L3!}dX zv!2arlZ;Y2Hc$Lj7@JfA7l6Z!^=9~2WK&2|4Sc)kPU zTR;;W=w-ey+DCe%dzjRi#6>EJ7K;>fUeR#akXn4fOgMcoO1u>sA2ob|lrQnJZ`pZD zxr*3zmUt(1A!_hQZeHDaqY!mnyyK%MU5ZE&#+2=X5cNBG^?a1ABwh#YXKF z-Qi|8Ra z=&r;Y-X7w$0%Fo{_OJryGdycdwE^<4=-%G>8jHsuII=aLLCxoN@4oq(5>K(<%+{msDDllQ!8xAw6oEbAo=*mYWhSaT-X0k_L#`|w0UNc(F;pwGWGx>?`6FbKVag6j2 z`ve6%@tzqL-;Bys?NQCbN!1`Q-G!2=kD!#Ko|(F|Q@%FA3--ZG`o%OkxEO8eTcUH`hwO^PZSS3MjJs1Bi~_&Q3@!GaLSY;9t!T3ZT^=iF91(*jF_Y* zh13}|f0U%ACmf?3?*L7BK@Ss<8>V{Zw1uA-EeaK{mE;_?S0(~em6QAhDReH1J`?>W zgyIg@Rq~>!M?B##2$qVsRQ&P;ssAtCB5|!Y5qv&X28#W6-5{A-h+y}TRO{r#DD59y zYyHSK7_#9wL#~AsF!D^FQ)2rfA`jk?%ur5I5=sQ-ImPmcYeyZDy^+~<%CbmvwAh7M z;>(~e2Y5vykgIXnmp$%8fYSz`R(eIIceCS0a=}D-CDU4_I3cTlRYUl0w$z4@|Is||~BK3@B0j~2~a>Na?LoF6= zRGWfMv0u)lw@ee>78(mCWUJ&C7}?t5P%@Qe#(`jt(HV5eW{E+Mbe;SZk6Va8_BKfx z!Pm(-lC-scRw4C9?Uv|3z;MQbQ8`@2m79Vwr?I6lLLi0EzYGePdmnU=8d=h^0d!#0 zZn*eX3Y3rC+h&(W$Xrsme*ikr5& z!s_v`fv)o?m35hs0jzO+xI!!u^T8`~bh5B(rkPslcq23sxld6gHX(8(!ZVJB%{o=F#|_YQh<`=l&nOA_UaK5+4EFos#l+xrR1CT(e5@-gqqbtPZ zxDnrT`T>PuVx)MjQ5*z!w`z5Ce9-U2Xq6BS`fp{tbmR$W3zuhpOwKD2(dRUzHeoEA z#$v2B@_D7yOAgSoi$XiG>POPQgvuJ4=bOmF+3wGqNBmk&ur`1 z)wC_XYm#UDirC|hZ*vPqaP+$_L`3dENTndYx7R=Pow!rep7WZ0`^ES{`Eu^ZN!9Un$Rd!8mmMEb(5U4+Hl;O%WgKC=JEY zcf*5>3cUm~Xrle2*R&9Gb06!!VDLqSr0$$Ci*utGJ{n~&MlnBDfKlqj^NGAwIYVE> zD6CGB{~0HIQC7Eg)f2+iQJH9m&Syv!O`SYpm6S%3DujNP1HNw6Ea2wm?C1Qt)liI+ z_0@#ufp_CYKCWV)9~>rGoy<~Q&_U_wWHe;4zrhY=(w~QXoA00l6j9L zWzt(GMd>8CCv?dOsbe)ZnFl^#>WpWfPU4g3ru#|iR}-J2nzra@WMsY}F8c&uVcnef}nG36yWE;Hh$Uik`1t@Vwl(Jj5?}eZDO@l_h%D)o1>oUYz6pq)#vN6 zF5Ti71&_s@v7Vfn!_6gjrLEn{S!jpf$btNSm~#sA)Uu_YD8{(Y*_9v53;G%7`00` z4=}Z`eY9Hje7VowlQgKS@>U= zq1F@2%N^dGkUzi;i)RCU#?GhD20Y){QPV+tv!Q3yXYuAmFRUixvsn9*>db1E{s~1i ztIX5pPQ%l5#2oGN>y0(*Kf`+S@y>dEMe@t48PJT6FPEIYto@^k-RADvj_Z=%-iRo5 z5B6}~wbJ@VL#hmrLEryq6wo-oB6&@c<-xH!D(M98GC1vXadH647?yAO5U zgZrfJMcFZtP*DXv@VO$todL$Fh(K z-l_dG5$x$HKN2X?ZTr}Iht5KG%hg_ec>lvLRtTQ5oXUsxBbbvt2_{K|Q^BV0#!*JN zOLVCbu`jE;sE{{|==S7Gv*{(7{5Dckqt<~wEE?xI>YPoN!0W>KcAkZ`DRI7CfQ-}q z6>`id-KiR(laumHIzFY2PGFy=K--a@GGt=J)5a!9?UJ>>>2cIA((1&JQcy2wh4sq; zyIX%L5i36taOx~zF413lc=y8&))^eLjOE+)!el%Vi7}!>;yjBQ9KPA~>$CT%WCi>J zsnJWXr7Gu1_imfTG3XAY9kH0&GA%hE#|^*@ym zvK8=LJ3_o($c7rNyPecmYILOqth5iJFV(3Jtf!oVJzuO#iOSxAfJ5)wX@q`Y z#?CZ>hw;z|*i;HK&!C+Hx262&+R>hbBl&8A?Ku>f&^gatVyW8uNNES(z#iysrxbAQ zD5VM}cI_y|3FfI85=Cua4DecEYgv>mb!(1RNA)f4a`k4FR58^@RD&lX+PX~VE}%s9jzFdQO@i1?+m^K<0*$%}MP=lBR{KTGEwEzk^FT9~0ZI)7OAB&x+v zT|w%t+8Sx87;}@ppaiLqD#YsU(mg$--lkoP8k>*weOfxF_?M8B5q0ZSf2OS6c1E_4%q#zbcB(-OvP!dEZNw`GkF~1N^)Ie9#69 zfuxGER6#vvne@=aQOXNI?SnD;=7wo?6#f~ELiYu{bNh>0_6_$1mT2Y|C|$42L_`yI z0<^e37QRelpQooc#&~)=xVU-HaKPBd?`Od2sDz1#*2GYNXm&mabpDrokL!{<=*ZxC zdkF6lxSQGp?uI$;2QO9Z_($AtekTopn_G3di%j4mJiHW-W1ms{P`fMlBfhv8yJazU zi&Ot;V)y?OM`ZSVoFh_xXcYZd4EDe}^jh>9&oO7BVm5tXda%b|F&K}XXd+gZ-#r+A zVvjn?`{F8mZ@^ow$ceM*;b}g0hm4hrvT6u=7z^8(5cdeFlsqLNj%QP+FrzswbT<9# z46xqE^f6Qps){I6zMu7M&ZghU*d#VJxS;iG88b>wE+~09;~w2#MR?aU4EgO(7}yR^ zg0GoHH{{7|`u1taq$tb7M+^@Ub}#m;vES6s)N+I7{>Rjk5X$#h`hUBNd7$pWrUzn= zO#U_f{DES@IyjqdoaP3teU?ER^#8{m{o$jQy@N__-(_Md;keBDLPR`%Hb1M6H-c8( zz(+jVF4_U_zk?SR>T=XANI`4JUFmn0LfW#ao(pEl9BT8$cE*TplK=~$ubKK2UjZHV z!C#_+KTcpRIEtGXQ`s(-hrHqoXWbG}V$Cn|YZl<@Gns+~PmP)4HaSwXpk!sn3(DC% z!wC5;36n$4lUOCU6}shRx#FY#l7LbBvH8QiTyex-F8kfQ_k%NXgrw{f)c#uo+nyJ& zP7dOjw|5;Qv*~Kc65luAyQs_2b3&)G64Tb&Puw@}L4Ow#2Fc-z z-3rS<^L>v4J*>eT-I$-zYd>v)PsP$2BW1TT{D8kNNMP|%NddayFMxkip9XE48X-dd zTBbI?#Q?mja;Kt1Wn0>&oy$$ZLAy5dP%S@=!g17 zSa=VN_3UZ3_3({qGxhNhawA}ZKd*%m3wIL!w1w56NvB$%vChG(j^v5%kV#x8KQ0sg zr}B_x@f)Oy7jt|kPf$McMrNzqxQsnbynoWb)1W1Eaz>#5TEzuue~dnxK0otYi&@L} z1_HY*Z>q)%P8EyL*!o>^3-Dt znH`CS%;NP*YlIy7E}3$IH+h&t8%-v0Tia!1HhssmGqh3fu_Yhk{fs5~ zw){NG82uHVNoM#1zC3lSFq`hY5DjAeMKSr8{TQ?TxbXJ*ar*YT@xN@tX50KMTYu(n z)-&1B^VBW^{{2@*n(Jl074fVV$pCh12D7Vt*0d%m!*knXvWeZn<1*(@%8q1MVK8r2 z>_rLVKPSI4{>c*N@GIl_LdbMZE=%T%iO6hvW2QjpEpxA9;MroeF9AohqfC9yTMqFR z>}!gR-%oZa#M2b~7Ftiv{swcN9nHUp>yo4euzV43Lr$4o_aD@25@W-OBB!VsVAqnB$K-L;yZtwcroF3dckK|okiTISEc;&a>R{_ z!7m*na3Aq4QL5MVL}9(^nRFNSH`Pn1{HA;*+ZJA6>f4VwnGVF8Jxur|xhc%VDNmyH z6C*`!=Fi^|wJl$w)kYt3{o#adIdjJL=|7w(u6-!D+~tKvp>(u|4X5hD=jGqWR>Y4* z!L93z7_TWaH&!?ra)$vO_2|v@>5-U7i~G}Bm1je+Tj_DGQz}AjN^^vCoh!>_m)ri} zrrH|Am$ne&dfD0HzZ#z*KjGxXh-@vU+Pu7ypG~isfaj9uIz|wiyFxtgBCI_`Z2ksL zwuM3pDYKSYS*wk(#CFOZ4;bHKcyq{aMC?zV7-drph-FYeD)n9Zi4h_{+6?pE0Q=UWmP3IL^v;C2K6U0hcrLvDJAvQp z1=oKUQIGEg80}Etoi`=7$-}Gdh<_D&XjH5ocTFx|f=%5!P2*-`CwR5VrQ-R{>@yG9 zL)VeotB5yXN6n_6gshtwStK#3uU;sIXTtI6;x-yzfxcZ=26@$vh@c{tB&mgO%9c7b z>OHV${S$WW7&wEX%%=YUt=*8d*Qj-}0xJ?$-lsASIG}c#I$~`#il5)fA^MpI$!3=J zARgfCIU0b^k1v5Z9z^~pMt3T=d_+m;At~tUJbM!yTD&B z1;<>moqqF@*Vz^28iUKd_GnfgDhrXyCZ`*o!a>Bei!^qH9nniVtD=y-!S9UiEDl}L zj%G&-*1kPnur@i)u9$$ghPE4X#drR|Q{azx&S_XCcnHyBWhwNj!R9vs+biHf6c6A2 zF+*O1o-ad|Hoe4kN z4(E)gdxr(K^-S8HF(%#csdLU*A--doN<8N=PB2EY{ijDv)%n+(fn~D7rnz|;Fj_nf zB7M_~{cnMGFjva#V+Z2kNX!7KHp*<0j{D9T@WD|E=YxPPege^HVDr1vM#PA>L#t&{ zXK~N!Uff+f&5I9?()rPO*hL4K*PXArz(-5Kl_*brA8{N?GfPI8)MBj4e4FYEh9L$K zjZcFr=F7@17zRnoy`WVge90qozJg-@%Cfmi(;wB)mhMRIV`GOg|I0=mzJbJ#j zp~;+KGHN<(@hp-Y4KwNL*<#7z87ns(o=Gp6EszR4@aEy(1&fk7Z1dFn3el=?*7mVp z_D(XeT#P&iYXDZk5Q|Y`ldpmfvU7pXO!+sH%>p2V$NYSd)26ItLVJTwALS6Spiv5H z`&_b4%>9F1+)zR-rv8^bvYS{e{UqQ1fWs#}kXT@$ID$+z;*rSOCo|A4=jCxk-2doe z{L zVffO=U?^Sy0|y&v4u(~Lf!b;Lu&SwEGLzmnlhsRV8iEV0G;{w@7dIGJ7iX>1Q4QCDDk=198B@0G1v%16UZqLT$bHMQCpS2pUD)3k}7*H}@He5B_8J+U0~y- z==jraPep4>@U-*b!x`u{Wi4M++*gyOi?tqd!}@r%_#(7V0orp5puGNN2a_G;V zg{OyX*mO*OcbpyYyKUOmN(bh3RAEv+w0uuz4~Jz6c_SeIB;2#$L(3&QtnTH&WVIGA zC(A1VwV)jboE3;klDbm8Hbil{NE-swivV@iM^NW$?NNK>XM+{3_G>VYorM;|8?s{b znzJ!f4xDi@AeN7UfRc}|X9_quG)#E5FH`jgDX*X4-B#^CuWy7;r&(hLN$MXc%vrC;O!~7K zZ@HhLCtJd3>#YWcr%c*~$?-k=qEcWA}lqHIekNL*t(Gu{sE@#N1!PVZfs+uN>f1Z92; zJ_ze3W-!rAwf3J}Z+L+b8e!A^D@OUVZa8e`r6B7euI|nP_#uk6Z|E3GFma1#sC*{< z=#=-NlNO8C#C;QMv*xMNq z6CDgZD@tbdIXV+5$hZZOSc@RR7k zJJ6jS0&&GV3n|97mz;Yf=}R)nH`wx;qJ3{k1Ha(-SDiP-;{;e2p*p3 zd@1y{A0fWe?(-JtpQR(s!R zXU;)4@Kh@Pa8-}fa)9fz_FY{*n6P|>2#-L9oz(w1nFoA)5$nQeX7QKf5ixH7n7D}D zS2|CRM|UEHl0-WVN9=sR7zh@)_U)YF4gP(d#r%C@s=2f&v*GP zU2l8~xZR;~wbumqb^dNUGB(AA+>^Sj~?&p;o`ONJ3s*$`aS(sp%^v!$2TtqK93yWL=B zFxi!T=fX9~!7kRy)K}{8%;YoPCE2wK1Pfhs|2yBP!kc`^hqWk0-q}og`-M3i&ta5O zjGV=PbSKRKh<4^D=hwP>XAE@K-C08Q^X->toYXL&CU*NO(Ky5m&ZNHz`9mHo^b+ln z-_Ty^j_TRT)TOmddfkQle>1nAX%15ie3|Oqlt-sV%wkQJ*VRZp!=&yrl-sSi7ToSr zS_AmpRl0CddaTlhqZaRFr`*|uV~`N)`pb$P8%rBP

;(G)hw@5b&Z^I8*`(5q$O1{}C}XrM z)OW|Jo_+qzIp}J1O~)X6nPDaiKV8=x(J@jQDr=$ID6buule8_QxcR1_L$W#(563K~*DW6SW}r2uCy2*^_Wo$bdqJDC*6grP5<6p- zKfXa*{pVk-Q)c8g>B(WDIKNPV{FnQ+?JKvvNi!XTfPY>}G*6Q%MKr3$j%v~LjQQ!I z6S*&}hX(p(Xx?VTxpIf{2WHam1A~jTd)$`g|8+(YAB|>~uhSd@Mr8bnl3VbV&ZG}a zFtrwM#cAUp+gC6^<2D7@8IP*t$KXIA?QFGHoX}5v`(XD zV>=%Wco9)tqgE#`Pf|#DYUgyUQOhJ-zulM9x2e$rEjyOA7~z9(a`3{N`<%R~&z|(r zXk15|`oC28(JjW;3ov#SC@nnU7(gzSm+L~bX->cGpnWsgNd-q{(kG^3FQEmU8*?8qiomLd)(^{Pl=2A(8?Jm*iMzLfL` zd=u4QRw^SE$tvMy@QHQGUie0QrINfj87sTrMQYZ`dH3fD#Y^pY_TTKY0?r)#Hi=^m6#&#T+sII3wh{&-F=-~BU^5lHm)V4$v{ZfdZHE- zt_iGxKUyUi#kOEoh)Vph)6+t#Ta~I+G3VFCmEz|_ved41?<^8eTRH8sX7lU0VrffQ z>^ZUcX6}k7#Kmo-r|R-+#7?nJ96FohbwP(C(B7GqMY@{xA`lnp$#0?Au(oVAY$39y z9NBExTxd}_8ubXxwPY%3+qs^SV#b09B)| z00UBRdns{2)7!`SrqgVOc$KI{qvebXzP7qttz?}&N#$1$GKT+``a%q~vm zud7|Jh~4|5*cYN))c)RpPegb0B@DYIxq~}=U|7-`H<%QC5{p3Q}8U&?|m1) zzkqM}t&n$^C!SYk(xcE#tnfTId}eu#e0k6#4k@(!KIBMHKM_u}go?ncevm0_i=Xg* z>Zsy(A4Nn;Zk$bj2C=faj5T{UV=V`t^E~folLq0aGM&C^meeuuxsfE{dDSs0W50T_ zZ;ik`%|%9>sLG6JB()fy8n)(hgN_D!KeN4VSJU?RgOjzZ{m2~L?iNgg)9H1y;4}|8 z<|2gm+u;Sb^>O{ye#Vp2&kc6($l9Kd<@QH*af8hLL%W*pk3W>=X%BPuWixyw zSJP?lY~CR0%N=C;@?RhVm2rg=)JkRI#lo6+Jf1&j%ti~j%i2W~vIR?k-K**eNsk1& z5PwF_ma*F7DY_MU_EMsi1YbRosk6MrcuIoo5v%P6^?+)qVUz4(wx(4@eD?BVJroZP zAvZ{B7bRbG7uQ+eI@WWltc7$DtFy{i)i9m@*BN98J-ZXx=Fhqg?k`mfZJ$+te&P}L zv7T=S-u>0lp0To{adyC@`e0d2j8tTExvtW9oY+9tOidD84)#tp&DkwVGBqELGu0E& zeK(vnK?k6E0QHW}q{*ZawJgml8%Q*h<%o774}yywRl=WW`TGg9tY(wvl%qHqLRP#9 zZu-A8>@CnmQ^F*%i30)S8Gj-kVBTcr_dnLwaq$Gp;_L|)XfHUyV#_z^-H#WV3-j|d zYd%vq|76h&xSu=Of@lqT$@WC(d6t;vQi)^4C^0)vAhab#u-aqwkzJ7Jv`G3ejccD;ny`^rB=*i(jb{4k}H- zC!MPwB6S6@RCY}uPJh2WSydYijyc);Njpw_g@|XL!bO_hALv|IMNYPk-EvRGA5IYO zk9EKVEMoqkaCo6{QQkWN|3sriYAIY|^w#7R();8k<#FeRr0lk6t6Hk~gNhs!L}aM9 zSHTPI1P^G(z8X8R(dku(q6t8|QuVfOP^$Q673iGKqA0WUa^3I{S=yiWn&km$H(Nd)^6pN$kX7kXG%+1S8Ji|NPs>4vU1wVB|F6};*ruq_GP8e zb_nw2NNGSO$!7U6neqHYX74{(!EUAz)SqNzH%^H}BY@(|IKQ!@;zP{zxxX^gIXRQb zJVFw~GwF9TOzMV-+7QFfqz%w3=J>#L`c#H^JRt*O4_AU)%$I&YGn;;52D;|-bUHOl zQooxasT;F#uj%wH(rWVi)iwiy=fG{O)1IA2QiU#46myUETzfs+*XPM7)ro@uDGcb54ta zbN|~EAFb>1<8%)wIh#k19Pg+_JnAk1zg^-rycvzg-1G5~a-NjMPDI4L*nK`%%>57` z{y=X;)|3ev7D=erF>hK0OEL=IBh`lS{1z<_xL4h@)&2H~7jLXXZyacbQXbtqA6=wj z#MEZQnMTsbgWD~dBG4DpdgJIgBsi` zcn^oHj9l?pFQ2f!@I2l!W;4J_5o2*epC$XpkXcuxAtKYwCk#V_Xi=mOJ?s_BeBktC zI{mY0tC;Wh)YdIqvqB^pe%1&6b+Ij#f_I4(YEIJZsYp_7lkKZb^2pWzEaNnzZ0SCB z=^szO2hM0gR^jpX)VKlC390g8A>!3j<8}KCb*9q>4=3=Tu2-ku7;lQQQI4ga-$cxy znUU{sy#0-F3-oL&|LbvshsIPKBjqjSAp@X$c-mylY2a%MiG(u)OMqt%S*@m|1Y8-B zi%N4|B}l@eRwM6}3tJg{9#T##M*<&c)9GE)j#2ua8Sl~7X?;39gt9(GSK@*H!c*VH z7UwmJV|wll)R8@ZhfhBd0=LrBHk6Bo+~&G8aw$L;Sz34V@NWj!IC-(aXBMW@U%G%3 zs`x&BAtMOe34d(ISdicC1sC}KEZahOs`;;9uV&*LsS$Y9gc}rk3I?Yx*3z>r6`MR_ zuYVD8)qBZC*COTn!9~zU7a@m(uS=boJLdu~!FqKhm*B+H#d!C0`Uf*)*`=p%&3L1{ z8cBpztL>ONlsJcPq2qi4xmUKM9V+H%2~q2APKKF6p(rEMIdQm_7S2t85|S>a9vYnAsF za`3^M>{^|oC|1S_P14#EE1gb%ZIYggqGvrExULb2rw-@A$kVgwWHyIqer$S%bWKEd zW9F$GXeV?hKZ)oHv?+@}g4IWk54|Um*a?b<3FuPJwXStvjoqzYu5MSqq1K9xh$%fk z@rc{Da`MdGF`D=CSVboNPNq4SFa1yXtFhO7M%ftO6JHbOcty>m|2NLPX3{TZ)}WQ1 zk%Ox-=1r4xQFBIkQLRH1bROTCks2vJOrlG~MbO}A_BX{X;$xs*iY2FR1m>yDPjg7! zZ4^s+TDXVr*$lbc<7`(B1k7(yePuYno_XH|Ir^4`|IIivkV!MZT&YIAn$yrwOOW-7 z++y4zQmd>IPr8_a>GYCG@-)Wr5T{0CUACEiuACAxCaAM>t=(^r0J)N=w z-X2^lmT44{I{1s^Z06a{Wv5G>W_gA4GKS3>N5rC9Y+r>|7TwfX*o~kXLzHpoi)4D=mnXZdUr} zIxIJ8Ls*AF7j^^IVPceGpT-*taK0HaKHMQnf_1nIcNPs9qoN=pPWbdpdQWDKmKW|D zi%58J-Cl7R+^hoe@HMT&>Sv=IVjY)@)zM-h612P6mAqzHnU7q5s{P0vRpkcwxM$Me zgJ;ADTaMKP(BCuD$UazU)tu@~dT9o73?92;*k(Pho0FT=KaDRB?MvL^{``q2VwGLU zRat7%|0(ez`)!=3Vh|N4!hX@Tl+i8qkWPXTzJEiK5iX2ppKIx}CCSuY7~h)s7xs&I zht$_XYgipD2yPY&f{V39EybbrYF|u>`YR+=0i2v_^CG9CkyDF#CD`OtMJv1qYo@)B z7w^r^Ag7ghpU3$fnn&~5;0C8T$$0*4+=n^C7v{?4t>yjW*<2Fv7^*A&|5&#$q7-xM zb}R1Y*6km0H@|MC?79siL!I)VOX~)EPbvz<5((rp6bU=dnl5Sw?_OY&+aZb5Sjm}m zV1dr{=7pK`mlkLU+Jy}NXJ01$ePm(G=>T)N85W89WvrS>GKGBb`aL}nKVgtmcz>Rw z+B^@Q4%<*2!HL_DY~%$=@BGO8@)pvaEcF zAqR!#qtHAQs(-_##2HOP&6UO_;xqo$@XP#2`818(S8X+ZI>;n17e|9XlFe(MArHA$ zD*qZ>DX&y&@kvi;z6-fUWmeYS@H9m?_>mPW&7?P9gc0@?U@VrHstv-{>OHb&%-G=w z6~IFFOyo)XaiW@$Gs5zQCfSD5>ZHcJjS;Qz@KXEIEM)}VXOjQium^mA*3e8#k}^%G zqw{CGY`X~PG^9Yr32f4eZ+PB2=vi80*FJ@XJmXNizw~q1qq-B6i*_8^-G~zQExnXM zX2RS%L~)GLQ|#@Y`|vPPJwd-ofY{+4eECR}!mn4mRJ3e7&hiLIpo21I8nq zb9EMqceJ0Q_1#nCuo}d9-9#@}7oq*Wj9EG~v+#Y?-Zo{%45X>+0Kq>_DZvKN!Y`*O z+{3y>_*zz%!2Uyr~eTeIQ)hLnfD**v^Py$%Hf2kYJ!h?RX&jstSnr~Hbv!? z?45lOcO>t;%6EA?_?=1z-$QJJ9Y+;nKcD4g$j=B76a6&`(8}8+nHpJqeU3{IEz6{{nzHc#Yvr3h4KPR7CcZDLhT2YjE__I2(L zb#;`inAUm&cAZ8_H@QeVANIRVx*fL505YSugdw>RQIT9#l}*ZLgQR_<%dt7&ejBh- zpK3ta;B12bl=9}eT*30T>WJdLLPM@kjb8~z{G}H5Gd5SUioY&+RH=8aSH2Y}e)Efw z&y?Qwr<44XU<(ISav#ra2nnB5 z4IyHV<+>a4)@RZ;KtsunkK9FWgz)99CRli+Z#sSSBiS{T?#wu8F1IwDzHdS4KV^!g zVw4j|`7H~|Z^`6I`6#Cz*?(M6etm|MY$&&*oW{lGc*Yd;N^W!yaIpFbhB&a#&*AqV z>Rx8ZTevtC(SeKSFBl>2XYm^ry}>}14rpw!aikgKU$CuBY9f1{j-D^0@|or{7KS8c%rutk z(j1L`czrCtU@e9D+|>uUgJgsARK%E!b2j9}^f;|b%rcgD($AS=hyfd5r-Dm?qfo9g z=f9i(?VUh$cXT6u>j?fgk0E*}Kvza>w#iM3Kk5W^a84RCC=tz{s}Ltllo1Zlv5!BKf8Gf%Q zM9l*>obI3N?YB57#7C{!Uv0U^-Iy2*octB-)rjmz^Wj*TzanuH;s0%%=^}N={M<$3 z6%-2v*usUlig3|i8Y>_M^0gxj%I3a{E`AqZ{I1l$_q*Gpl_mf2ULk!AF^Wv)5PVhd z{|iHMaeWQ+@+6*5;@*nuc|7mKa{=y6xSqo^{hcT(L-ye7?rw&>j%P}9ujLbR^`i_4 zFow%A8T*o4Cqwda*_@1FN7bJUD>E6^$rv}EdY&QcKlaOLjlbIWt2{tMufO}3(YA)S zQ|Rd*L$;EuH+{U%oR^+BMp$%+D;Hn)gzt^qdB?WxE84f+ckixT8;?KFe4*!R{$f$K z7{AJPcVtU=d*r@*KVFlq#ZR)`erM$FJ5c5Mdw;{tvutie+5!FZ0QPVmBCbP~BY zvi){7yydPtw%?BW@LvL(9c#gKZr^c7=eD_NUA}7d<*To}YSrp1J^|Yte0S`KY`Nq1 zPJCT#f8`ZdT=w5J-QJ~Al>E(RcSbtzc_7?*$L(7>w=`UdmaA4>(Rk%w8++S*cW&Q0 zKYj;V-tuweQ2^;*gU|Pu6}Nr7=%GuG7jNhSF5A>4#`(~Ve{k@C) z$zDH6)^mLl^38ZD!kT|quUOSj_WDV}zSQfz=yjf=vt+)$)numC2K+-=CWo)5PyYwZ CIz7Yy literal 0 HcmV?d00001 diff --git a/app/src/main/res/raw/dfu_mac_3_0.sh b/app/src/main/res/raw/dfu_mac_3_1.sh similarity index 92% rename from app/src/main/res/raw/dfu_mac_3_0.sh rename to app/src/main/res/raw/dfu_mac_3_1.sh index 98883ff79..1ec7ceab3 100644 --- a/app/src/main/res/raw/dfu_mac_3_0.sh +++ b/app/src/main/res/raw/dfu_mac_3_1.sh @@ -55,6 +55,8 @@ INIT_FILE="" INIT_PATH="" E_INIT_FILE="" TYPE=0 +# Modify this in order to keep the bond information after application upgrade +KEEP_BOND=false # ================================================================================== # Common methods @@ -186,11 +188,11 @@ fi if [ "$ADDRESS" = "" ] ; then # Start DFU Initiator activity if no target device specified printf "Starting DFU Initiator activity..." - adb $S_DEVICE shell am start -a no.nordicsemi.android.action.DFU_UPLOAD --ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE $TYPE -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/$HEX_FILE" $E_INIT_FILE | grep "Error" > /dev/null 2>&1 + adb $S_DEVICE shell am start -a no.nordicsemi.android.action.DFU_UPLOAD --ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE $TYPE --ez no.nordicsemi.android.dfu.extra.EXTRA_KEEP_BOND $KEEP_BOND -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/$HEX_FILE" $E_INIT_FILE | grep "Error" > /dev/null 2>&1 else # Start the DFU service directly printf "Starting DFU service..." - adb $S_DEVICE shell am startservice -n no.nordicsemi.android.nrftoolbox/.dfu.DfuService -a no.nordicsemi.android.action.DFU_UPLOAD --ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE $TYPE -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_ADDRESS $ADDRESS -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_NAME $NAME -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/$HEX_FILE" $E_INIT_FILE | grep "Error" > /dev/null 2>&1 + adb $S_DEVICE shell am startservice -n no.nordicsemi.android.nrftoolbox/.dfu.DfuService -a no.nordicsemi.android.action.DFU_UPLOAD --ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE --ez no.nordicsemi.android.dfu.extra.EXTRA_KEEP_BOND $KEEP_BOND -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_ADDRESS $ADDRESS -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_NAME $NAME -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/$HEX_FILE" $E_INIT_FILE | grep "Error" > /dev/null 2>&1 fi if [ "$?" = "0" ] ; then diff --git a/app/src/main/res/raw/dfu_win_3_0.bat b/app/src/main/res/raw/dfu_win_3_1.bat similarity index 97% rename from app/src/main/res/raw/dfu_win_3_0.bat rename to app/src/main/res/raw/dfu_win_3_1.bat index 2cce233c1..ced47100a 100644 --- a/app/src/main/res/raw/dfu_win_3_0.bat +++ b/app/src/main/res/raw/dfu_win_3_1.bat @@ -53,6 +53,8 @@ set INIT_FILE= set INIT_PATH= set E_INIT_FILE= set TYPE=0 +rem Modify this in order to keep the bond information +set KEEP_BOND=false rem ================================================================================== rem Check ADB @@ -195,6 +197,7 @@ if "%ADDRESS%"=="" ( echo|set /p=Starting DFU Initiator activity... call adb %S_DEVICE% shell am start -a no.nordicsemi.android.action.DFU_UPLOAD^ --ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE %TYPE%^ + --ez no.nordicsemi.android.dfu.extra.EXTRA_KEEP_BOND %KEEP_BOND%^ -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/%HEX_FILE%" %E_INIT_FILE% | find "Error" > nul 2>&1 ) else ( rem Start DFU service on the device @@ -203,6 +206,7 @@ if "%ADDRESS%"=="" ( -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_ADDRESS %ADDRESS%^ -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_NAME %NAME%^ --ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE %TYPE%^ + --ez no.nordicsemi.android.dfu.extra.EXTRA_KEEP_BOND %KEEP_BOND%^ -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/%HEX_FILE%" %E_INIT_FILE% | find "Error" > nul 2>&1 ) diff --git a/app/src/main/res/values/strings_dfu.xml b/app/src/main/res/values/strings_dfu.xml index edbc155c2..c8643a9d8 100644 --- a/app/src/main/res/values/strings_dfu.xml +++ b/app/src/main/res/values/strings_dfu.xml @@ -73,24 +73,28 @@ Number of packets MBR size MBR size (default 4096 = 0x1000) + Keep bond information About DFU DFU documentation on Nordic\'s Developer Zone Information - During a DFU operation a lot of data packets are being sent to the target. The onCharacteristicWrite(...) callback - in Android API is being invoked when the data has been written to the outgoing queue, not when physically sent. Packet Receipt Notifications were introduced to prevent from - overflowing the queue. Disabling them or setting the value to high (> ~300) may cause the DFU process to freeze at some point, depending on the device model. + During a DFU operation a lot of data packets are being sent to the target. The onCharacteristicWrite(...) + callback in Android API is invoked when the data has been written to the outgoing queue, not when physically sent. Packet receipt notifications were introduced to + prevent from overflowing the queue. Depending on the device model, disabling the notifications or setting the value to “high” (> ~300) may make the DFU process freeze + at some point. Select file type - Soft Device - Bootloader - Application - Multiple files (ZIP) + Distribution packet (ZIP) + Soft Device + Bootloader + Application Init packet Do you want to select the Init packet file?\n - \nThe Init packet file (*.dat) should contain the device type and revision, application version, list of supported Soft Devices and the firmware CRC in binary format or, in case of old - versions of the DFU bootloader, only the CRC (CRC-CCITT-16). In case of the new version of the bootloader the extended Init packet is required. + The Init packet file (*.dat) should contain the device type and revision, application version, list of supported Soft Devices and the firmware CRC + in binary format or, with old versions of the DFU bootloader, only the CRC (CRC-CCITT-16). + With the new version of the bootloader the extended Init packet is required. + unnamed device 0% @@ -104,16 +108,19 @@ Select file A file browser application must be installed on the device before selecting the file. - \n\nThere are number of applications available on Google Play store, f.e. Total Commander or File Manager, that allow you to pick a file from internal memory of the device. To upload + \n\nThere are number of applications available on Google Play store, e.g. Total Commander or File Manager, that allow you to pick a file from internal memory of the device. To upload a file from the Internet you may use f.e. Drive or Dropbox application. \n\nYou will be asked to select an application if more than one is installed. A single application will be launched automatically. \n\nSince Android KitKat you may use the preinstalled document picker application. Ensure Display advanced devices option is enabled in settings to use the Internal storage. - \n\nHeart Rate Monitor and Running Speed and Cadence applications were copied to Nordic Semiconductor folder in the internal storage. + \n\nSample applications were copied to Nordic Semiconductor folder in the internal storage. - With the DFU you may update your application, Soft Device or the Booloader Over-The-Air. For each type a HEX or a BIN file may be provided. - \nIn order to update the Soft Device together with a compatible Bootloader put them into a ZIP file and name according to the image below. - \nYou may also provide the Init packet(s) that will be send to verify the firmware on the device side. More information about the Init packet is in the DFU documentation. - The Device Firmware Update (DFU) app allows users to update the firmware of their Bluetooth Smart device over-the-air (OTA). It is compatible with - Nordic Semiconductor nRF51822 or nRF51422 devices with the S110 SoftDevice and DFU bootloader enabled. With the SoftDevice 7.0.0+ a SoftDevice itself and/or a bootloader may also be updated. - \n\nFor more information about the DFU see the About DFU section in Settings. + Starting from nRF Toolbox v1.12 the new Distribution packet (ZIP) is the recommended method for distributing firmware upgrades. + You can create the ZIP file using the nrf utility tool, which is part of Master Control Panel 3.8.0+. For more detailed information, see the DFU documentation. + \n\nBackward compatibility + \nThe nRF Toolbox also supports all old file formats: HEX and BIN files, separate DAT files and ZIP files without a manifest file but with a fixed naming convention: + + The Device Firmware Update (DFU) app allows you to update the firmware of your Bluetooth Smart device over-the-air (OTA). + It is compatible with Nordic Semiconductor nRF51822 or nRF51422 devices with S110 SoftDevice and DFU bootloader enabled. With SoftDevice s110 7.0.0+, + the SoftDevice itself and/or a bootloader may also be updated. + \n\nFor more information about the DFU, see the About DFU section in Settings. diff --git a/app/src/main/res/values/strings_proximity.xml b/app/src/main/res/values/strings_proximity.xml index 8990f1964..b37f0cfc8 100644 --- a/app/src/main/res/values/strings_proximity.xml +++ b/app/src/main/res/values/strings_proximity.xml @@ -27,8 +27,8 @@ -142dp DEFAULT PROXIMITY - FindMe - SilentMe + Find Me + Silent Me LOCK Image Disconnect %s is getting away! diff --git a/app/src/main/res/xml/settings_dfu.xml b/app/src/main/res/xml/settings_dfu.xml index 75ac0e6db..844be712e 100644 --- a/app/src/main/res/xml/settings_dfu.xml +++ b/app/src/main/res/xml/settings_dfu.xml @@ -41,6 +41,10 @@ android:key="settings_mbr_size" android:dialogTitle="@string/dfu_settings_dfu_mbr_size_title" android:title="@string/dfu_settings_dfu_mbr_size" /> + - + @@ -7,9 +7,7 @@ - - - +