Version 1.12

BleManagers refactoring, support for keeping bond information in DFU
(DFU v0.6), support for Distribution packets in DFU.
This commit is contained in:
Aleksander Nowakowski
2015-03-13 17:04:36 +01:00
parent c245a33c48
commit b0a078fe4f
87 changed files with 3611 additions and 2439 deletions

2
.idea/modules.xml generated
View File

@@ -2,6 +2,8 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/../...iml" filepath="$PROJECT_DIR$/../...iml" />
<module fileurl="file://$PROJECT_DIR$/../DFULibrary/DFULibrary.iml" filepath="$PROJECT_DIR$/../DFULibrary/DFULibrary.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" /> <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/../DFULibrary/dfu/dfu.iml" filepath="$PROJECT_DIR$/../DFULibrary/dfu/dfu.iml" /> <module fileurl="file://$PROJECT_DIR$/../DFULibrary/dfu/dfu.iml" filepath="$PROJECT_DIR$/../DFULibrary/dfu/dfu.iml" />
<module fileurl="file://$PROJECT_DIR$/nRFToolbox.iml" filepath="$PROJECT_DIR$/nRFToolbox.iml" /> <module fileurl="file://$PROJECT_DIR$/nRFToolbox.iml" filepath="$PROJECT_DIR$/nRFToolbox.iml" />

View File

@@ -85,6 +85,7 @@
</content> </content>
<orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" /> <orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="gson-2.3.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-21.0.3" level="project" /> <orderEntry type="library" exported="" name="appcompat-v7-21.0.3" level="project" />
<orderEntry type="library" exported="" name="nrf-logger-v2.0" level="project" /> <orderEntry type="library" exported="" name="nrf-logger-v2.0" level="project" />
<orderEntry type="library" exported="" name="support-annotations-21.0.3" level="project" /> <orderEntry type="library" exported="" name="support-annotations-21.0.3" level="project" />

View File

@@ -9,8 +9,8 @@ android {
applicationId "no.nordicsemi.android.nrftoolbox" applicationId "no.nordicsemi.android.nrftoolbox"
minSdkVersion 18 minSdkVersion 18
targetSdkVersion 21 targetSdkVersion 21
versionCode 28 versionCode 29
versionName "1.11.5" versionName "1.12.0"
} }
buildTypes { buildTypes {
release { release {

View File

@@ -23,8 +23,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="no.nordicsemi.android.nrftoolbox" package="no.nordicsemi.android.nrftoolbox"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="28" android:versionCode="29"
android:versionName="1.11.5" > android:versionName="1.12.0" >
<uses-sdk <uses-sdk
android:minSdkVersion="18" android:minSdkVersion="18"

View File

@@ -21,9 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox; package no.nordicsemi.android.nrftoolbox;
import java.util.List;
import no.nordicsemi.android.nrftoolbox.adapter.AppAdapter;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
@@ -49,6 +46,10 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import java.util.List;
import no.nordicsemi.android.nrftoolbox.adapter.AppAdapter;
public class FeaturesActivity extends ActionBarActivity { public class FeaturesActivity extends ActionBarActivity {
private static final String MCP_CATEGORY = "no.nordicsemi.android.nrftoolbox.LAUNCHER"; private static final String MCP_CATEGORY = "no.nordicsemi.android.nrftoolbox.LAUNCHER";
private static final String UTILS_CATEGORY = "no.nordicsemi.android.nrftoolbox.UTILS"; private static final String UTILS_CATEGORY = "no.nordicsemi.android.nrftoolbox.UTILS";

View File

@@ -21,11 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.adapter; package no.nordicsemi.android.nrftoolbox.adapter;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import no.nordicsemi.android.nrftoolbox.R;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -38,6 +33,12 @@ import android.widget.BaseAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import no.nordicsemi.android.nrftoolbox.R;
public class AppAdapter extends BaseAdapter { public class AppAdapter extends BaseAdapter {
private static final String CATEGORY = "no.nordicsemi.android.nrftoolbox.LAUNCHER"; private static final String CATEGORY = "no.nordicsemi.android.nrftoolbox.LAUNCHER";
private static final String MCP_PACKAGE = "no.nordicsemi.android.mcp"; private static final String MCP_PACKAGE = "no.nordicsemi.android.mcp";

View File

@@ -16,10 +16,6 @@
package no.nordicsemi.android.nrftoolbox.app; package no.nordicsemi.android.nrftoolbox.app;
import java.util.List;
import java.util.Map;
import no.nordicsemi.android.nrftoolbox.R;
import android.app.Activity; import android.app.Activity;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
@@ -33,6 +29,11 @@ import android.widget.ExpandableListView;
import android.widget.SimpleCursorTreeAdapter; import android.widget.SimpleCursorTreeAdapter;
import android.widget.SimpleExpandableListAdapter; import android.widget.SimpleExpandableListAdapter;
import java.util.List;
import java.util.Map;
import no.nordicsemi.android.nrftoolbox.R;
/** /**
* An activity that displays an expandable list of items by binding to a data source implementing the ExpandableListAdapter, and exposes event handlers when the * An activity that displays an expandable list of items by binding to a data source implementing the ExpandableListAdapter, and exposes event handlers when the
* user selects an item. * user selects an item.

View File

@@ -21,15 +21,17 @@
*/ */
package no.nordicsemi.android.nrftoolbox.bpm; package no.nordicsemi.android.nrftoolbox.bpm;
import android.os.Bundle;
import android.widget.TextView;
import java.util.Calendar; import java.util.Calendar;
import java.util.UUID; import java.util.UUID;
import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.profile.BleManager; import no.nordicsemi.android.nrftoolbox.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.profile.BleProfileActivity; import no.nordicsemi.android.nrftoolbox.profile.BleProfileActivity;
import android.os.Bundle;
import android.widget.TextView;
// TODO The BPMActivity should be rewritten to use the service approach, like other do.
public class BPMActivity extends BleProfileActivity implements BPMManagerCallbacks { public class BPMActivity extends BleProfileActivity implements BPMManagerCallbacks {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String TAG = "BPMActivity"; private static final String TAG = "BPMActivity";
@@ -60,6 +62,11 @@ public class BPMActivity extends BleProfileActivity implements BPMManagerCallbac
mTimestampView = (TextView) findViewById(R.id.timestamp); mTimestampView = (TextView) findViewById(R.id.timestamp);
} }
@Override
protected int getLoggerProfileTitle() {
return R.string.bpm_feature_title;
}
@Override @Override
protected int getDefaultDeviceName() { protected int getDefaultDeviceName() {
return R.string.bpm_default_name; return R.string.bpm_default_name;
@@ -77,7 +84,7 @@ public class BPMActivity extends BleProfileActivity implements BPMManagerCallbac
@Override @Override
protected BleManager<BPMManagerCallbacks> initializeManager() { protected BleManager<BPMManagerCallbacks> initializeManager() {
final BPMManager manager = BPMManager.getBPMManager(); final BPMManager manager = BPMManager.getBPMManager(getApplicationContext());
manager.setGattCallbacks(this); manager.setGattCallbacks(this);
return manager; return manager;
} }
@@ -100,12 +107,7 @@ public class BPMActivity extends BleProfileActivity implements BPMManagerCallbac
} }
@Override @Override
public void onBloodPressureMeasurementIndicationsEnabled() { public void onDeviceReady() {
// this may notify user
}
@Override
public void onIntermediateCuffPressureNotificationEnabled() {
// this may notify user // this may notify user
} }

View File

@@ -21,181 +21,107 @@
*/ */
package no.nordicsemi.android.nrftoolbox.bpm; package no.nordicsemi.android.nrftoolbox.bpm;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import java.util.Calendar; import java.util.Calendar;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID; import java.util.UUID;
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; 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<BPMManagerCallbacks> {
private final String TAG = "BPMManager";
private BPMManagerCallbacks mCallbacks;
private BluetoothGatt mBluetoothGatt;
private Context mContext;
public class BPMManager extends BleManager<BPMManagerCallbacks> {
/** Blood Pressure service UUID */
public final static UUID BP_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb"); 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 UUID */
/** Blood Pressure Measurement characteristic */
private static final UUID BPM_CHARACTERISTIC_UUID = UUID.fromString("00002A35-0000-1000-8000-00805f9b34fb"); 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"); 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 BluetoothGattCharacteristic mBPMCharacteristic, mICPCharacteristic;
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 static BPMManager managerInstance = null; private static BPMManager managerInstance = null;
/** /**
* Returns the singleton implementation of BPMManager * Returns the singleton implementation of BPMManager
*/ */
public static synchronized BPMManager getBPMManager() { public static synchronized BPMManager getBPMManager(final Context context) {
if (managerInstance == null) { if (managerInstance == null) {
managerInstance = new BPMManager(); managerInstance = new BPMManager(context);
} }
return managerInstance; return managerInstance;
} }
/** private BPMManager(final Context context) {
* Callbacks for activity {@link BPMActivity} that implements {@link BPMManagerCallbacks} interface activity use this method to register itself for receiving callbacks super(context);
*/
@Override
public void setGattCallbacks(final BPMManagerCallbacks callbacks) {
mCallbacks = callbacks;
} }
@Override @Override
public void connect(final Context context, final BluetoothDevice device) { protected BleManagerGattCallback getGattCallback() {
mBluetoothGatt = device.connectGatt(context, false, mGattCallback); return mGattCallback;
mContext = context;
} }
@Override
public void disconnect() {
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
}
}
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();
}
}
};
/** /**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc
*/ */
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() {
@Override @Override
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { protected Queue<Request> initGatt(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final LinkedList<Request> requests = new LinkedList<>();
if (newState == BluetoothProfile.STATE_CONNECTED) { if (mICPCharacteristic != null)
mCallbacks.onDeviceConnected(); requests.push(Request.newEnableNotificationsRequest(mICPCharacteristic));
// start discovering services requests.push(Request.newEnableIndicationsRequest(mBPMCharacteristic));
gatt.discoverServices(); return requests;
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
mCallbacks.onDeviceDisconnected();
gatt.close();
}
} else {
mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
}
} }
@Override @Override
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) { protected boolean isRequiredServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { BluetoothGattService service = gatt.getService(BP_SERVICE_UUID);
for (BluetoothGattService service : gatt.getServices()) { if (service != null) {
if (BP_SERVICE_UUID.equals(service.getUuid())) { mBPMCharacteristic = service.getCharacteristic(BPM_CHARACTERISTIC_UUID);
mBPMCharacteristic = service.getCharacteristic(BPM_CHARACTERISTIC_UUID); mICPCharacteristic = service.getCharacteristic(ICP_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);
// 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);
} }
return mBPMCharacteristic != null;
} }
@Override @Override
public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { protected boolean isOptionalServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { return mICPCharacteristic != null;
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
if (mICPCharacteristic != null)
enableIntermediateCuffPressureNotification(gatt);
else
enableBloodPressureMeasurementIndication(gatt);
}
} else {
mCallbacks.onError(ERROR_DISCOVERY_SERVICE, status);
}
} }
@Override @Override
public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { protected void onDeviceDisconnected() {
// ICP or BPM characteristic returned value mICPCharacteristic = null;
mBPMCharacteristic = null;
}
@Override
protected void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
// Intermediate Cuff Pressure characteristic read
if (mLogSession != null)
Logger.a(mLogSession, IntermediateCuffPressureParser.parse(characteristic));
parseBPMValue(characteristic);
}
@Override
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 // first byte - flags
int offset = 0; int offset = 0;
@@ -206,14 +132,14 @@ public class BPMManager implements BleManager<BPMManagerCallbacks> {
final boolean pulseRatePresent = (flags & 0x04) > 0; final boolean pulseRatePresent = (flags & 0x04) > 0;
if (BPM_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) { 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 systolic = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset);
final float diastolic = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 2); final float diastolic = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 2);
final float meanArterialPressure = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 4); final float meanArterialPressure = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 4);
offset += 6; offset += 6;
mCallbacks.onBloodPressureMeasurementRead(systolic, diastolic, meanArterialPressure, unit); mCallbacks.onBloodPressureMeasurementRead(systolic, diastolic, meanArterialPressure, unit);
} else if (ICP_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) { } 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); final float cuffPressure = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset);
offset += 6; offset += 6;
mCallbacks.onIntermediateCuffPressureRead(cuffPressure, unit); mCallbacks.onIntermediateCuffPressureRead(cuffPressure, unit);
@@ -223,7 +149,7 @@ public class BPMManager implements BleManager<BPMManagerCallbacks> {
if (timestampPresent) { if (timestampPresent) {
final Calendar calendar = Calendar.getInstance(); final Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset)); 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.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.HOUR_OF_DAY, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 4));
calendar.set(Calendar.MINUTE, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 5)); calendar.set(Calendar.MINUTE, characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 5));
@@ -236,81 +162,10 @@ public class BPMManager implements BleManager<BPMManagerCallbacks> {
// parse pulse rate if present // parse pulse rate if present
if (pulseRatePresent) { if (pulseRatePresent) {
final float pulseRate = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); final float pulseRate = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset);
offset += 2; // offset += 2;
mCallbacks.onPulseRateRead(pulseRate); mCallbacks.onPulseRateRead(pulseRate);
} else } else
mCallbacks.onPulseRateRead(-1.0f); 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;
}
}
} }

View File

@@ -29,16 +29,6 @@ public interface BPMManagerCallbacks extends BleManagerCallbacks {
public static final int UNIT_mmHG = 0; public static final int UNIT_mmHG = 0;
public static final int UNIT_kPa = 1; 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 * Called when new BPM value has been obtained from the sensor
* *

View File

@@ -22,13 +22,6 @@
package no.nordicsemi.android.nrftoolbox.csc; 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.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -40,6 +33,14 @@ import android.support.v4.content.LocalBroadcastManager;
import android.view.Menu; import android.view.Menu;
import android.widget.TextView; 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<CSCService.CSCBinder> { public class CSCActivity extends BleProfileServiceReadyActivity<CSCService.CSCBinder> {
private TextView mSpeedView; private TextView mSpeedView;
private TextView mSpeedUnitView; private TextView mSpeedUnitView;

View File

@@ -22,188 +22,70 @@
package no.nordicsemi.android.nrftoolbox.csc; package no.nordicsemi.android.nrftoolbox.csc;
import java.util.List; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import no.nordicsemi.android.log.ILogSession;
import no.nordicsemi.android.log.Logger; import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.parser.CSCMeasurementParser;
import no.nordicsemi.android.nrftoolbox.profile.BleManager; 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<CSCManagerCallbacks> { public class CSCManager extends BleManager<CSCManagerCallbacks> {
private static final String TAG = "RSCManager"; /** Cycling Speed and Cadence service UUID */
public final static UUID CYCLING_SPEED_AND_CADENCE_SERVICE_UUID = UUID.fromString("00001816-0000-1000-8000-00805f9b34fb");
private CSCManagerCallbacks mCallbacks; /** Cycling Speed and Cadence Measurement characteristic UUID */
private BluetoothGatt mBluetoothGatt; private static final UUID CSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A5B-0000-1000-8000-00805f9b34fb");
private Context mContext;
private ILogSession mLogSession;
private static final byte WHEEL_REVOLUTIONS_DATA_PRESENT = 0x01; // 1 bit private static final byte WHEEL_REVOLUTIONS_DATA_PRESENT = 0x01; // 1 bit
private static final byte CRANK_REVOLUTION_DATA_PRESENT = 0x02; // 1 bit private static final byte CRANK_REVOLUTION_DATA_PRESENT = 0x02; // 1 bit
public final static UUID CYCLING_SPEED_AND_CADENCE_SERVICE_UUID = UUID.fromString("00001816-0000-1000-8000-00805f9b34fb"); private BluetoothGattCharacteristic mCSCMeasurementCharacteristic;
/** Cycling Speed and Cadence Measurement characteristic */
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 BluetoothGattCharacteristic mCSCMeasurementCharacteristic, mBatteryCharacteristic;
private boolean mBatteryLevelNotificationsEnabled;
public CSCManager(final Context context) { public CSCManager(final Context context) {
// Register bonding broadcast receiver super(context);
final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
context.registerReceiver(mBondingBroadcastReceiver, filter);
} }
@Override @Override
public void setGattCallbacks(final CSCManagerCallbacks callbacks) { protected BleManagerGattCallback getGattCallback() {
mCallbacks = callbacks; return mGattCallback;
}
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();
}
} }
/** /**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
*/ */
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() {
@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. @Override
mCallbacks.onDeviceDisconnected(); protected Queue<Request> initGatt(final BluetoothGatt gatt) {
closeBluetoothGatt(); final LinkedList<Request> requests = new LinkedList<>();
} requests.push(Request.newEnableNotificationsRequest(mCSCMeasurementCharacteristic));
} else { return requests;
mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
}
} }
@Override @Override
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) { public boolean isRequiredServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final BluetoothGattService service = gatt.getService(CYCLING_SPEED_AND_CADENCE_SERVICE_UUID);
final List<BluetoothGattService> services = gatt.getServices(); if (service != null) {
for (BluetoothGattService service : services) { mCSCMeasurementCharacteristic = service.getCharacteristic(CSC_MEASUREMENT_CHARACTERISTIC_UUID);
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);
} }
return mCSCMeasurementCharacteristic != null;
} }
@Override @Override
public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { protected void onDeviceDisconnected() {
if (status == BluetoothGatt.GATT_SUCCESS) { mCSCMeasurementCharacteristic = null;
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);
}
} }
@Override @Override
public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) { public void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
if (status == BluetoothGatt.GATT_SUCCESS) { if (mLogSession != null)
if (descriptor.getCharacteristic().getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) { Logger.a(mLogSession, CSCMeasurementParser.parse(characteristic));
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);
}
}
@Override
public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
// Decode the new data // Decode the new data
int offset = 0; int offset = 0;
final int flags = characteristic.getValue()[offset]; // 1 byte final int flags = characteristic.getValue()[offset]; // 1 byte
@@ -212,110 +94,27 @@ public class CSCManager implements BleManager<CSCManagerCallbacks> {
final boolean wheelRevPresent = (flags & WHEEL_REVOLUTIONS_DATA_PRESENT) > 0; final boolean wheelRevPresent = (flags & WHEEL_REVOLUTIONS_DATA_PRESENT) > 0;
final boolean crankRevPreset = (flags & CRANK_REVOLUTION_DATA_PRESENT) > 0; final boolean crankRevPreset = (flags & CRANK_REVOLUTION_DATA_PRESENT) > 0;
int wheelRevolutions = 0;
int lastWheelEventTime = 0;
if (wheelRevPresent) { if (wheelRevPresent) {
wheelRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, offset); final int wheelRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, offset);
offset += 4; 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; offset += 2;
// Notify listener about the new measurement // Notify listener about the new measurement
mCallbacks.onWheelMeasurementReceived(wheelRevolutions, lastWheelEventTime); mCallbacks.onWheelMeasurementReceived(wheelRevolutions, lastWheelEventTime);
} }
int crankRevolutions = 0;
int lastCrankEventTime = 0;
if (crankRevPreset) { if (crankRevPreset) {
crankRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); final int crankRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
offset += 2; offset += 2;
lastCrankEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); final int lastCrankEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
offset += 2; // offset += 2;
// Notify listener about the new measurement // Notify listener about the new measurement
mCallbacks.onCrankMeasurementReceived(crankRevolutions, lastCrankEventTime); 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;
}
} }

View File

@@ -22,12 +22,6 @@
package no.nordicsemi.android.nrftoolbox.csc; 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.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@@ -36,10 +30,16 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.IBinder;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager; 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 { public class CSCService extends BleProfileService implements CSCManagerCallbacks {
private static final String TAG = "CSCService"; 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 static final String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.csc.ACTION_DISCONNECT";
private CSCManager mManager; private CSCManager mManager;
private boolean mBinded;
private int mFirstWheelRevolutions = -1; private int mFirstWheelRevolutions = -1;
private int mLastWheelRevolutions = -1; private int mLastWheelRevolutions = -1;
@@ -73,7 +72,7 @@ public class CSCService extends BleProfileService implements CSCManagerCallbacks
private final LocalBinder mBinder = new CSCBinder(); 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 { public class CSCBinder extends LocalBinder {
// empty // empty
@@ -108,28 +107,15 @@ public class CSCService extends BleProfileService implements CSCManagerCallbacks
} }
@Override @Override
public IBinder onBind(final Intent intent) { protected void onRebind() {
mBinded = true;
return super.onBind(intent);
}
@Override
public void onRebind(final Intent intent) {
mBinded = true;
// when the activity rebinds to the service, remove the notification // when the activity rebinds to the service, remove the notification
cancelNotification(); cancelNotification();
// read the battery level when back in the Activity
if (isConnected())
mManager.readBatteryLevel();
} }
@Override @Override
public boolean onUnbind(final Intent intent) { protected void onUnbind() {
mBinded = false; // when the activity closes we need to show the notification that user is connected to the sensor
// 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); createNotification(R.string.csc_notification_connected_message, 0);
return super.onUnbind(intent);
} }
@Override @Override
@@ -140,6 +126,8 @@ public class CSCService extends BleProfileService implements CSCManagerCallbacks
@Override @Override
public void onWheelMeasurementReceived(final int wheelRevolutions, final int lastWheelEventTime) { 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 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final int circumference = Integer.parseInt(preferences.getString(SettingsFragment.SETTINGS_WHEEL_SIZE, String.valueOf(SettingsFragment.SETTINGS_WHEEL_SIZE_DEFAULT))); // [mm] final int circumference = Integer.parseInt(preferences.getString(SettingsFragment.SETTINGS_WHEEL_SIZE, String.valueOf(SettingsFragment.SETTINGS_WHEEL_SIZE_DEFAULT))); // [mm]
@@ -173,6 +161,8 @@ public class CSCService extends BleProfileService implements CSCManagerCallbacks
@Override @Override
public void onCrankMeasurementReceived(int crankRevolutions, int lastCrankEventTime) { public void onCrankMeasurementReceived(int crankRevolutions, int lastCrankEventTime) {
Logger.a(getLogSession(), "Crank rev: " + crankRevolutions + "\nLast crank event time: " + lastCrankEventTime + " ms");
if (mLastCrankEventTime == lastCrankEventTime) if (mLastCrankEventTime == lastCrankEventTime)
return; return;
@@ -241,7 +231,7 @@ public class CSCService extends BleProfileService implements CSCManagerCallbacks
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(final Context context, final Intent intent) { 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()) if (isConnected())
getBinder().disconnect(); getBinder().disconnect();
else else

View File

@@ -22,12 +22,13 @@
package no.nordicsemi.android.nrftoolbox.csc.settings; package no.nordicsemi.android.nrftoolbox.csc.settings;
import no.nordicsemi.android.nrftoolbox.R;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import no.nordicsemi.android.nrftoolbox.R;
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String SETTINGS_WHEEL_SIZE = "settings_wheel_size"; public static final String SETTINGS_WHEEL_SIZE = "settings_wheel_size";
public static final int SETTINGS_WHEEL_SIZE_DEFAULT = 2340; public static final int SETTINGS_WHEEL_SIZE_DEFAULT = 2340;

View File

@@ -72,6 +72,7 @@ import no.nordicsemi.android.nrftoolbox.dfu.adapter.FileBrowserAppsAdapter;
import no.nordicsemi.android.nrftoolbox.dfu.fragment.UploadCancelFragment; import no.nordicsemi.android.nrftoolbox.dfu.fragment.UploadCancelFragment;
import no.nordicsemi.android.nrftoolbox.dfu.fragment.ZipInfoFragment; import no.nordicsemi.android.nrftoolbox.dfu.fragment.ZipInfoFragment;
import no.nordicsemi.android.nrftoolbox.dfu.settings.SettingsActivity; 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.scanner.ScannerFragment;
import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
@@ -85,7 +86,7 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks<Cu
private static final String TAG = "DfuActivity"; private static final String TAG = "DfuActivity";
private static final String PREFS_SAMPLES_VERSION = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_SAMPLES_VERSION"; private static final String PREFS_SAMPLES_VERSION = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_SAMPLES_VERSION";
private static final int CURRENT_SAMPLES_VERSION = 2; private static final int CURRENT_SAMPLES_VERSION = 3;
private static final String PREFS_DEVICE_NAME = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_DEVICE_NAME"; private static final String PREFS_DEVICE_NAME = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_DEVICE_NAME";
private static final String PREFS_FILE_NAME = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_FILE_NAME"; private static final String PREFS_FILE_NAME = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_FILE_NAME";
@@ -168,7 +169,7 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks<Cu
ensureSamplesExist(); ensureSamplesExist();
// restore saved state // restore saved state
mFileType = DfuService.TYPE_APPLICATION; // Default mFileType = DfuService.TYPE_AUTO; // Default
if (savedInstanceState != null) { if (savedInstanceState != null) {
mFileType = savedInstanceState.getInt(DATA_FILE_TYPE); mFileType = savedInstanceState.getInt(DATA_FILE_TYPE);
mFileTypeTmp = savedInstanceState.getInt(DATA_FILE_TYPE_TMP); mFileTypeTmp = savedInstanceState.getInt(DATA_FILE_TYPE_TMP);
@@ -307,9 +308,10 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks<Cu
new File(root, "ble_app_rscs_s110_v7_0_0.hex").delete(); new File(root, "ble_app_rscs_s110_v7_0_0.hex").delete();
new File(root, "blinky_arm_s110_v7_0_0.hex").delete(); new File(root, "blinky_arm_s110_v7_0_0.hex").delete();
new File(root, "dfu_2_0.bat").delete(); // This file has been migrated to 3.0 new File(root, "dfu_2_0.bat").delete(); // This file has been migrated to 3.0
new File(root, "dfu_3_0.bat").delete(); // This file has been modified - bug fixed new File(root, "dfu_3_0.bat").delete(); // This file has been migrated to 3.1
new File(root, "dfu_2_0.sh").delete(); // This file has been migrated to 3.0 new File(root, "dfu_2_0.sh").delete(); // This file has been migrated to 3.0
new File(root, "README.txt").delete(); // This file has been modified to match v.3.0 new File(root, "dfu_3_0.sh").delete(); // This file has been migrated to 3.1
new File(root, "README.txt").delete(); // This file has been modified to match v.3.0+
boolean oldCopied = false; boolean oldCopied = false;
boolean newCopied = false; boolean newCopied = false;
@@ -361,6 +363,11 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks<Cu
copyRawResource(R.raw.ble_app_hrs_dfu_s110_v7_1_0_ext_init, f); copyRawResource(R.raw.ble_app_hrs_dfu_s110_v7_1_0_ext_init, f);
oldCopied = true; oldCopied = true;
} }
f = new File(pca10028, "ble_app_hrs_dfu_s110_v8_0_0.zip");
if (!f.exists()) {
copyRawResource(R.raw.ble_app_hrs_dfu_s110_v8_0_0, f);
newCopied = true;
}
if (oldCopied) if (oldCopied)
Toast.makeText(this, R.string.dfu_example_files_created, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.dfu_example_files_created, Toast.LENGTH_SHORT).show();
@@ -369,14 +376,14 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks<Cu
// Scripts // Scripts
newCopied = false; newCopied = false;
f = new File(root, "dfu_3_0.bat"); f = new File(root, "dfu_3_1.bat");
if (!f.exists()) { if (!f.exists()) {
copyRawResource(R.raw.dfu_win_3_0, f); copyRawResource(R.raw.dfu_win_3_1, f);
newCopied = true; newCopied = true;
} }
f = new File(root, "dfu_3_0.sh"); f = new File(root, "dfu_3_1.sh");
if (!f.exists()) { if (!f.exists()) {
copyRawResource(R.raw.dfu_mac_3_0, f); copyRawResource(R.raw.dfu_mac_3_1, f);
newCopied = true; newCopied = true;
} }
f = new File(root, "README.txt"); f = new File(root, "README.txt");
@@ -568,16 +575,16 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks<Cu
private void updateFileInfo(final String fileName, final long fileSize, final int fileType) { private void updateFileInfo(final String fileName, final long fileSize, final int fileType) {
mFileNameView.setText(fileName); mFileNameView.setText(fileName);
switch (fileType) { switch (fileType) {
case DfuService.TYPE_SOFT_DEVICE: case DfuService.TYPE_AUTO:
mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[0]); mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[0]);
break; break;
case DfuService.TYPE_BOOTLOADER: case DfuService.TYPE_SOFT_DEVICE:
mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[1]); mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[1]);
break; break;
case DfuService.TYPE_APPLICATION: case DfuService.TYPE_BOOTLOADER:
mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[2]); mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[2]);
break; break;
case DfuService.TYPE_AUTO: case DfuService.TYPE_APPLICATION:
mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[3]); mFileTypeView.setText(getResources().getStringArray(R.array.dfu_file_type)[3]);
break; break;
} }
@@ -625,20 +632,20 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks<Cu
*/ */
public void onSelectFileClicked(final View view) { public void onSelectFileClicked(final View view) {
mFileTypeTmp = mFileType; mFileTypeTmp = mFileType;
int index = 2; int index = 0;
switch (mFileType) { switch (mFileType) {
case DfuService.TYPE_SOFT_DEVICE: case DfuService.TYPE_AUTO:
index = 0; index = 0;
break; break;
case DfuService.TYPE_SOFT_DEVICE:
index = 1;
break;
case DfuService.TYPE_BOOTLOADER: case DfuService.TYPE_BOOTLOADER:
index = 1; index = 2;
break; break;
case DfuService.TYPE_APPLICATION: case DfuService.TYPE_APPLICATION:
index = 2; index = 3;
break; break;
case DfuService.TYPE_AUTO:
index = 3;
break;
} }
// Show a dialog with file types // Show a dialog with file types
new AlertDialog.Builder(this).setTitle(R.string.dfu_file_type_title) new AlertDialog.Builder(this).setTitle(R.string.dfu_file_type_title)
@@ -647,16 +654,16 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks<Cu
public void onClick(final DialogInterface dialog, final int which) { public void onClick(final DialogInterface dialog, final int which) {
switch (which) { switch (which) {
case 0: case 0:
mFileTypeTmp = DfuService.TYPE_SOFT_DEVICE; mFileTypeTmp = DfuService.TYPE_AUTO;
break; break;
case 1: case 1:
mFileTypeTmp = DfuService.TYPE_BOOTLOADER; mFileTypeTmp = DfuService.TYPE_SOFT_DEVICE;
break; break;
case 2: case 2:
mFileTypeTmp = DfuService.TYPE_APPLICATION; mFileTypeTmp = DfuService.TYPE_BOOTLOADER;
break; break;
case 3: case 3:
mFileTypeTmp = DfuService.TYPE_AUTO; mFileTypeTmp = DfuService.TYPE_APPLICATION;
break; break;
} }
} }
@@ -734,6 +741,8 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks<Cu
showProgressBar(); showProgressBar();
final boolean keepBond = preferences.getBoolean(SettingsFragment.SETTINGS_KEEP_BOND, false);
final Intent service = new Intent(this, DfuService.class); final Intent service = new Intent(this, DfuService.class);
service.putExtra(DfuService.EXTRA_DEVICE_ADDRESS, mSelectedDevice.getAddress()); service.putExtra(DfuService.EXTRA_DEVICE_ADDRESS, mSelectedDevice.getAddress());
service.putExtra(DfuService.EXTRA_DEVICE_NAME, mSelectedDevice.getName()); service.putExtra(DfuService.EXTRA_DEVICE_NAME, mSelectedDevice.getName());
@@ -743,6 +752,7 @@ public class DfuActivity extends ActionBarActivity implements LoaderCallbacks<Cu
service.putExtra(DfuService.EXTRA_FILE_URI, mFileStreamUri); service.putExtra(DfuService.EXTRA_FILE_URI, mFileStreamUri);
service.putExtra(DfuService.EXTRA_INIT_FILE_PATH, mInitFilePath); service.putExtra(DfuService.EXTRA_INIT_FILE_PATH, mInitFilePath);
service.putExtra(DfuService.EXTRA_INIT_FILE_URI, mInitFileStreamUri); service.putExtra(DfuService.EXTRA_INIT_FILE_URI, mInitFileStreamUri);
service.putExtra(DfuService.EXTRA_KEEP_BOND, keepBond);
startService(service); startService(service);
} }

View File

@@ -22,12 +22,13 @@
package no.nordicsemi.android.nrftoolbox.dfu; package no.nordicsemi.android.nrftoolbox.dfu;
import no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment;
import android.app.Activity; import android.app.Activity;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; 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 * 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}. * selected device using {@link DfuService}.
@@ -58,6 +59,7 @@ public class DfuInitiatorActivity extends Activity implements ScannerFragment.On
final String address = device.getAddress(); final String address = device.getAddress();
final String finalName = overwrittenName == null ? name : overwrittenName; final String finalName = overwrittenName == null ? name : overwrittenName;
final int type = intent.getIntExtra(DfuService.EXTRA_FILE_TYPE, DfuService.TYPE_AUTO); 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 // Start DFU service with data provided in the intent
final Intent service = new Intent(this, DfuService.class); final Intent service = new Intent(this, DfuService.class);
@@ -67,6 +69,7 @@ public class DfuInitiatorActivity extends Activity implements ScannerFragment.On
service.putExtra(DfuService.EXTRA_FILE_PATH, path); service.putExtra(DfuService.EXTRA_FILE_PATH, path);
if (intent.hasExtra(DfuService.EXTRA_INIT_FILE_PATH)) if (intent.hasExtra(DfuService.EXTRA_INIT_FILE_PATH))
service.putExtra(DfuService.EXTRA_INIT_FILE_PATH, initPath); service.putExtra(DfuService.EXTRA_INIT_FILE_PATH, initPath);
service.putExtra(DfuService.EXTRA_KEEP_BOND, keepBond);
startService(service); startService(service);
finish(); finish();
} }

View File

@@ -22,9 +22,10 @@
package no.nordicsemi.android.nrftoolbox.dfu; package no.nordicsemi.android.nrftoolbox.dfu;
import no.nordicsemi.android.dfu.DfuBaseService;
import android.app.Activity; import android.app.Activity;
import no.nordicsemi.android.dfu.DfuBaseService;
public class DfuService extends DfuBaseService { public class DfuService extends DfuBaseService {
@Override @Override

View File

@@ -22,11 +22,12 @@
package no.nordicsemi.android.nrftoolbox.dfu; package no.nordicsemi.android.nrftoolbox.dfu;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
public class NotificationActivity extends Activity { public class NotificationActivity extends Activity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {

View File

@@ -21,7 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.dfu.adapter; package no.nordicsemi.android.nrftoolbox.dfu.adapter;
import no.nordicsemi.android.nrftoolbox.R;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -30,6 +29,8 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import android.widget.TextView; 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 * 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. * URLs are specified in res/values/strings_dfu.xml.

View File

@@ -21,8 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.dfu.fragment; 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.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
@@ -33,6 +31,9 @@ import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.util.Log; 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 * When cancel button is pressed during uploading this fragment shows uploading cancel dialog
*/ */

View File

@@ -21,7 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.dfu.fragment; package no.nordicsemi.android.nrftoolbox.dfu.fragment;
import no.nordicsemi.android.nrftoolbox.R;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
@@ -29,6 +28,8 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import no.nordicsemi.android.nrftoolbox.R;
public class ZipInfoFragment extends DialogFragment { public class ZipInfoFragment extends DialogFragment {
@Override @Override

View File

@@ -22,7 +22,6 @@
package no.nordicsemi.android.nrftoolbox.dfu.settings; package no.nordicsemi.android.nrftoolbox.dfu.settings;
import no.nordicsemi.android.nrftoolbox.R;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
@@ -30,6 +29,8 @@ import android.preference.Preference;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.Toast; import android.widget.Toast;
import no.nordicsemi.android.nrftoolbox.R;
public class AboutDfuPreference extends Preference { public class AboutDfuPreference extends Preference {
public AboutDfuPreference(Context context, AttributeSet attrs) { public AboutDfuPreference(Context context, AttributeSet attrs) {
@@ -43,7 +44,7 @@ public class AboutDfuPreference extends Preference {
@Override @Override
protected void onClick() { protected void onClick() {
final Context context = getContext(); 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.addCategory(Intent.CATEGORY_DEFAULT);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

View File

@@ -21,15 +21,17 @@
*/ */
package no.nordicsemi.android.nrftoolbox.dfu.settings; 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.app.AlertDialog;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen; 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 class SettingsFragment extends PreferenceFragment implements DfuSettingsConstants, SharedPreferences.OnSharedPreferenceChangeListener {
public static final String SETTINGS_KEEP_BOND = "settings_keep_bond";
@Override @Override
public void onCreate(final Bundle savedInstanceState) { public void onCreate(final Bundle savedInstanceState) {

View File

@@ -21,7 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.gls; package no.nordicsemi.android.nrftoolbox.gls;
import no.nordicsemi.android.nrftoolbox.R;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.util.Pair; import android.util.Pair;
@@ -31,6 +30,8 @@ import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter; import android.widget.BaseExpandableListAdapter;
import android.widget.TextView; import android.widget.TextView;
import no.nordicsemi.android.nrftoolbox.R;
public class ExpandableRecordAdapter extends BaseExpandableListAdapter { public class ExpandableRecordAdapter extends BaseExpandableListAdapter {
private final GlucoseManager mGlucoseManager; private final GlucoseManager mGlucoseManager;
private final LayoutInflater mInflater; private final LayoutInflater mInflater;

View File

@@ -21,11 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.gls; 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.os.Bundle;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.MenuInflater; import android.view.MenuInflater;
@@ -35,6 +30,13 @@ import android.widget.BaseExpandableListAdapter;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.TextView; 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 { public class GlucoseActivity extends BleProfileExpandableListActivity implements PopupMenu.OnMenuItemClickListener, GlucoseManagerCallbacks {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String TAG = "GlucoseActivity"; private static final String TAG = "GlucoseActivity";
@@ -95,7 +97,7 @@ public class GlucoseActivity extends BleProfileExpandableListActivity implements
@Override @Override
protected BleManager<GlucoseManagerCallbacks> initializeManager() { protected BleManager<GlucoseManagerCallbacks> initializeManager() {
GlucoseManager manager = mGlucoseManager = GlucoseManager.getGlucoseManager(); GlucoseManager manager = mGlucoseManager = GlucoseManager.getGlucoseManager(getApplicationContext());
manager.setGattCallbacks(this); manager.setGattCallbacks(this);
return manager; return manager;
} }
@@ -119,6 +121,11 @@ public class GlucoseActivity extends BleProfileExpandableListActivity implements
return true; return true;
} }
@Override
protected int getLoggerProfileTitle() {
return R.string.gls_feature_title;
}
@Override @Override
protected int getAboutTextId() { protected int getAboutTextId() {
return R.string.gls_about_text; return R.string.gls_about_text;
@@ -151,23 +158,9 @@ public class GlucoseActivity extends BleProfileExpandableListActivity implements
} }
@Override @Override
public void onServicesDiscovered(boolean optionalServicesFound) { public void onDeviceDisconnected() {
// this may notify user or show some views super.onDeviceDisconnected();
} setOperationInProgress(false);
@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
} }
@Override @Override
@@ -197,6 +190,12 @@ public class GlucoseActivity extends BleProfileExpandableListActivity implements
showToast(R.string.gls_operation_failed); showToast(R.string.gls_operation_failed);
} }
@Override
public void onError(final String message, final int errorCode) {
super.onError(message, errorCode);
onOperationFailed();
}
@Override @Override
public void onDatasetChanged() { public void onDatasetChanged() {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {

View File

@@ -21,51 +21,39 @@
*/ */
package no.nordicsemi.android.nrftoolbox.gls; 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.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler; import android.os.Handler;
import android.util.SparseArray; import android.util.SparseArray;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;
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;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class GlucoseManager implements BleManager<GlucoseManagerCallbacks> { public class GlucoseManager extends BleManager<GlucoseManagerCallbacks> {
private static final String TAG = "GlucoseManager"; private static final String TAG = "GlucoseManager";
private GlucoseManagerCallbacks mCallbacks; /** Glucose service UUID */
private BluetoothGatt mBluetoothGatt;
private Context mContext;
private Handler mHandler;
private boolean mAbort;
private final SparseArray<GlucoseRecord> mRecords = new SparseArray<>();
public final static UUID GLS_SERVICE_UUID = UUID.fromString("00001808-0000-1000-8000-00805f9b34fb"); 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 UUID */
/** Glucose Measurement characteristic */
private final static UUID GM_CHARACTERISTIC = UUID.fromString("00002A18-0000-1000-8000-00805f9b34fb"); 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"); 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"); 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"); 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_REPORT_STORED_RECORDS = 1;
private final static int OP_CODE_DELETE_STORED_RECORDS = 2; private final static int OP_CODE_DELETE_STORED_RECORDS = 2;
@@ -105,314 +93,80 @@ public class GlucoseManager implements BleManager<GlucoseManagerCallbacks> {
private final static int RESPONSE_PROCEDURE_NOT_COMPLETED = 8; private final static int RESPONSE_PROCEDURE_NOT_COMPLETED = 8;
private final static int RESPONSE_OPERAND_NOT_SUPPORTED = 9; 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 mGlucoseMeasurementCharacteristic;
private BluetoothGattCharacteristic mGlucoseFeatureCharacteristic;
private BluetoothGattCharacteristic mGlucoseMeasurementContextCharacteristic; private BluetoothGattCharacteristic mGlucoseMeasurementContextCharacteristic;
private BluetoothGattCharacteristic mRecordAccessControlPointCharacteristic; private BluetoothGattCharacteristic mRecordAccessControlPointCharacteristic;
private BluetoothGattCharacteristic mBatteryLevelCharacteristic;
private final SparseArray<GlucoseRecord> mRecords = new SparseArray<>();
private boolean mAbort;
private Handler mHandler;
private static GlucoseManager mInstance; private static GlucoseManager mInstance;
/** /**
* Returns the singleton implementation of GlucoseManager * Returns the singleton implementation of GlucoseManager
*/ */
public static GlucoseManager getGlucoseManager() { public static GlucoseManager getGlucoseManager(final Context context) {
if (mInstance == null) if (mInstance == null)
mInstance = new GlucoseManager(); mInstance = new GlucoseManager(context);
return mInstance; return mInstance;
} }
/** public GlucoseManager(final Context context) {
* Callbacks for activity {@link GlucoseActivity} that implements {@link GlucoseManagerCallbacks} interface activity use this method to register itself for receiving callbacks super(context);
*/ mHandler = new Handler();
@Override
public void setGattCallbacks(final GlucoseManagerCallbacks callbacks) {
mCallbacks = callbacks;
}
/**
* Returns all records as a sparse array where sequence number is the key.
*
* @return the records list
*/
public SparseArray<GlucoseRecord> getRecords() {
return mRecords;
} }
@Override @Override
public void connect(final Context context, final BluetoothDevice device) { protected BleManagerGattCallback getGattCallback() {
if (mHandler == null) return mGattCallback;
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);
}
/**
* 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.
* <p>
* 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()}).
* </p>
*/
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.
*/
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 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();
}
}
};
/** /**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc
*/ */
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() {
@Override @Override
public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { protected Queue<Request> initGatt(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final LinkedList<Request> requests = new LinkedList<>();
if (newState == BluetoothProfile.STATE_CONNECTED) { requests.push(Request.newEnableNotificationsRequest(mGlucoseMeasurementCharacteristic));
mCallbacks.onDeviceConnected(); if (mGlucoseMeasurementContextCharacteristic != null)
// start discovering services requests.push(Request.newEnableNotificationsRequest(mGlucoseMeasurementContextCharacteristic));
gatt.discoverServices(); requests.push(Request.newEnableIndicationsRequest(mRecordAccessControlPointCharacteristic));
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) { return requests;
mCallbacks.onDeviceDisconnected();
gatt.close();
}
} else {
DebugLogger.e(TAG, "onConnectionStateChange error " + status);
mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
}
} }
@Override @Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) { public boolean isRequiredServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final BluetoothGattService service = gatt.getService(GLS_SERVICE_UUID);
for (BluetoothGattService service : gatt.getServices()) { if (service != null) {
if (GLS_SERVICE_UUID.equals(service.getUuid())) { mGlucoseMeasurementCharacteristic = service.getCharacteristic(GM_CHARACTERISTIC);
mGlucoseMeasurementCharacteristic = service.getCharacteristic(GM_CHARACTERISTIC); mGlucoseMeasurementContextCharacteristic = service.getCharacteristic(GM_CONTEXT_CHARACTERISTIC);
mGlucoseMeasurementContextCharacteristic = service.getCharacteristic(GM_CONTEXT_CHARACTERISTIC); mRecordAccessControlPointCharacteristic = service.getCharacteristic(RACP_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);
} }
return mGlucoseMeasurementCharacteristic != null && mRecordAccessControlPointCharacteristic != null;
} }
@Override @Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { protected boolean isOptionalServiceSupported(BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { return mGlucoseMeasurementContextCharacteristic != null;
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);
}
} }
@Override @Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { protected void onDeviceDisconnected() {
if (status == BluetoothGatt.GATT_SUCCESS) { mGlucoseMeasurementCharacteristic = null;
if (GM_CHARACTERISTIC.equals(descriptor.getCharacteristic().getUuid())) { mGlucoseMeasurementContextCharacteristic = null;
mCallbacks.onGlucoseMeasurementNotificationEnabled(); mRecordAccessControlPointCharacteristic = null;
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);
}
} }
@Override @Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { public void onCharacteristicNotified(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
final UUID uuid = characteristic.getUuid(); final UUID uuid = characteristic.getUuid();
if (GM_CHARACTERISTIC.equals(uuid)) { if (GM_CHARACTERISTIC.equals(uuid)) {
if (mLogSession != null)
Logger.a(mLogSession, GlucoseMeasurementParser.parse(characteristic));
int offset = 0; int offset = 0;
final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
offset += 1; offset += 1;
@@ -487,6 +241,9 @@ public class GlucoseManager implements BleManager<GlucoseManagerCallbacks> {
} }
}); });
} else if (GM_CONTEXT_CHARACTERISTIC.equals(uuid)) { } else if (GM_CONTEXT_CHARACTERISTIC.equals(uuid)) {
if (mLogSession != null)
Logger.a(mLogSession, GlucoseMeasurementContextParser.parse(characteristic));
int offset = 0; int offset = 0;
final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
offset += 1; offset += 1;
@@ -552,92 +309,62 @@ public class GlucoseManager implements BleManager<GlucoseManagerCallbacks> {
// notify callback about the new record // notify callback about the new record
mCallbacks.onDatasetChanged(); 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
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);
mCallbacks.onNumberOfRecordsRequested(number);
// Request the records
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:
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;
}
} }
} }
/** @Override
* Reads battery level on the device protected void onCharacteristicIndicated(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
*/ if (mLogSession != null)
private void readBatteryLevel(final BluetoothGatt gatt) { Logger.a(mLogSession, RecordAccessControlPointParser.parse(characteristic));
DebugLogger.d(TAG, "readBatteryLevel()");
gatt.readCharacteristic(mBatteryLevelCharacteristic); // Record Access Control Point characteristic
int offset = 0;
final int opCode = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
offset += 2; // skip the operator
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);
mCallbacks.onNumberOfRecordsRequested(number);
// Request the records
if (number > 0) {
final BluetoothGattCharacteristic racpCharacteristic = mRecordAccessControlPointCharacteristic;
setOpCode(racpCharacteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_ALL_RECORDS);
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();
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;
}
} }
}; };
/**
* 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 * Writes given operation parameters to the characteristic
* *
@@ -675,24 +402,127 @@ public class GlucoseManager implements BleManager<GlucoseManagerCallbacks> {
} }
} }
} }
/**
* Returns all records as a sparse array where sequence number is the key.
*
* @return the records list
*/
public SparseArray<GlucoseRecord> getRecords() {
return mRecords;
}
@Override /**
public void closeBluetoothGatt() { * Clears the records list locally
try { */
mContext.unregisterReceiver(mBondingBroadcastReceiver); public void clear() {
} catch (Exception e) { mRecords.clear();
// the receiver must have been not registered or unregistered before mCallbacks.onDatasetChanged();
} }
if (mBluetoothGatt != null) { /**
mBluetoothGatt.close(); * 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
mRecords.clear(); * Control Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error.
mGlucoseMeasurementCharacteristic = null; */
mGlucoseMeasurementContextCharacteristic = null; public void getLastRecord() {
mGlucoseFeatureCharacteristic = null; if (mRecordAccessControlPointCharacteristic == null)
mRecordAccessControlPointCharacteristic = null; return;
mBatteryLevelCharacteristic = null;
mBluetoothGatt = null; 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.
* <p>
* 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()}).
* </p>
*/
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);
}
} }

View File

@@ -27,12 +27,6 @@ public interface GlucoseManagerCallbacks extends BleManagerCallbacks {
public static final int UNIT_mmHG = 0; public static final int UNIT_mmHG = 0;
public static final int UNIT_kPa = 1; public static final int UNIT_kPa = 1;
public void onGlucoseMeasurementNotificationEnabled();
public void onGlucoseMeasurementContextNotificationEnabled();
public void onRecordAccessControlPointIndicationsEnabled();
public void onOperationStarted(); public void onOperationStarted();
public void onOperationCompleted(); public void onOperationCompleted();

View File

@@ -21,35 +21,36 @@
*/ */
package no.nordicsemi.android.nrftoolbox.hrs; 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.graphics.Point;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; 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 * 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. * 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 { public class HRSActivity extends BleProfileActivity implements HRSManagerCallbacks {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final String TAG = "HRSActivity"; private final String TAG = "HRSActivity";
private static final String GRAPH_STATUS = "graph_status"; private final static String GRAPH_STATUS = "graph_status";
private static final String GRAPH_COUNTER = "graph_counter"; private final static String GRAPH_COUNTER = "graph_counter";
private static final String HR_VALUE = "hr_value"; private final static String HR_VALUE = "hr_value";
private final int MAX_HR_VALUE = 65535; private final static int MAX_HR_VALUE = 65535;
private final int MIN_POSITIVE_VALUE = 0; private final static int MIN_POSITIVE_VALUE = 0;
private final int REFRESH_INTERVAL = 1000; // 1 second interval private final static int REFRESH_INTERVAL = 1000; // 1 second interval
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
@@ -63,7 +64,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
private int mCounter = 0; private int mCounter = 0;
@Override @Override
protected void onCreateView(Bundle savedInstanceState) { protected void onCreateView(final Bundle savedInstanceState) {
setContentView(R.layout.activity_feature_hrs); setContentView(R.layout.activity_feature_hrs);
setGUI(); setGUI();
} }
@@ -82,7 +83,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
} }
@Override @Override
protected void onRestoreInstanceState(Bundle savedInstanceState) { protected void onRestoreInstanceState(final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState); super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null) { if (savedInstanceState != null) {
@@ -96,7 +97,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
} }
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putBoolean(GRAPH_STATUS, isGraphInProgress); outState.putBoolean(GRAPH_STATUS, isGraphInProgress);
@@ -111,6 +112,11 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
stopShowGraph(); stopShowGraph();
} }
@Override
protected int getLoggerProfileTitle() {
return R.string.hrs_feature_title;
}
@Override @Override
protected int getAboutTextId() { protected int getAboutTextId() {
return R.string.hrs_about_text; return R.string.hrs_about_text;
@@ -154,7 +160,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
@Override @Override
protected BleManager<HRSManagerCallbacks> initializeManager() { protected BleManager<HRSManagerCallbacks> initializeManager() {
HRSManager manager = HRSManager.getInstance(this); final HRSManager manager = HRSManager.getInstance(getApplicationContext());
manager.setGattCallbacks(this); manager.setGattCallbacks(this);
return manager; return manager;
} }
@@ -186,18 +192,18 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
} }
@Override @Override
public void onServicesDiscovered(boolean optionalServicesFound) { public void onServicesDiscovered(final boolean optionalServicesFound) {
// this may notify user or show some views // this may notify user or show some views
} }
@Override @Override
public void onHRSensorPositionFound(String position) { public void onDeviceReady() {
setHRSPositionOnView(position); startShowGraph();
} }
@Override @Override
public void onHRNotificationEnabled() { public void onHRSensorPositionFound(final String position) {
startShowGraph(); setHRSPositionOnView(position);
} }
@Override @Override

View File

@@ -21,231 +21,132 @@
*/ */
package no.nordicsemi.android.nrftoolbox.hrs; package no.nordicsemi.android.nrftoolbox.hrs;
import java.util.List; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.R; 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; 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;
/** /**
* HRSManager class performs BluetoothGatt operations for connection, service discovery, enabling notification and reading characteristics. All operations required to connect to device with BLE HR * 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 * 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<HRSManagerCallbacks> { public class HRSManager extends BleManager<HRSManagerCallbacks> {
private final String TAG = "HRSManager";
private HRSManagerCallbacks mCallbacks;
private BluetoothGatt mBluetoothGatt;
private Context mContext;
public final static UUID HR_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb"); 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_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 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 BluetoothGattCharacteristic mHRCharacteristic, mHRLocationCharacteristic;
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 static HRSManager managerInstance = null; private static HRSManager managerInstance = null;
/** /**
* singleton implementation of HRSManager class * singleton implementation of HRSManager class
*/ */
public static synchronized HRSManager getInstance(Context context) { public static synchronized HRSManager getInstance(final Context context) {
if (managerInstance == null) { if (managerInstance == null) {
managerInstance = new HRSManager(); managerInstance = new HRSManager(context);
} }
managerInstance.mContext = context;
return managerInstance; return managerInstance;
} }
/** public HRSManager(final Context context) {
* callbacks for activity {HRSActivity} that implements HRSManagerCallbacks interface activity use this method to register itself for receiving callbacks super(context);
*/
@Override
public void setGattCallbacks(HRSManagerCallbacks callbacks) {
mCallbacks = callbacks;
} }
@Override @Override
public void connect(Context context, BluetoothDevice device) { protected BleManagerGattCallback getGattCallback() {
DebugLogger.d(TAG, "Connecting to device..."); return mGattCallback;
mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
}
/**
* Disable HR notification first and then disconnect to HR device
*/
@Override
public void disconnect() {
DebugLogger.d(TAG, "Disconnecting device...");
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
}
} }
/** /**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc
*/ */
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() {
@Override @Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { protected Queue<Request> initGatt(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final LinkedList<Request> requests = new LinkedList<>();
if (newState == BluetoothProfile.STATE_CONNECTED) { if (mHRLocationCharacteristic != null)
DebugLogger.d(TAG, "Device connected"); requests.push(Request.newReadRequest(mHRLocationCharacteristic));
mBluetoothGatt.discoverServices(); requests.push(Request.newEnableNotificationsRequest(mHRCharacteristic));
//This will send callback to HRSActivity when device get connected return requests;
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);
}
} }
@Override @Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) { protected boolean isRequiredServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final BluetoothGattService service = gatt.getService(HR_SERVICE_UUID);
List<BluetoothGattService> services = gatt.getServices(); if (service != null) {
for (BluetoothGattService service : services) { mHRCharacteristic = service.getCharacteristic(HR_CHARACTERISTIC_UUID);
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);
} }
return mHRCharacteristic != null;
} }
@Override @Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { protected boolean isOptionalServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final BluetoothGattService service = gatt.getService(HR_SERVICE_UUID);
if (characteristic.getUuid().equals(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID)) { if (service != null) {
final String sensorPosition = getBodySensorPosition(characteristic.getValue()[0]); mHRLocationCharacteristic = service.getCharacteristic(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID);
//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);
} }
return mHRLocationCharacteristic != null;
} }
@Override @Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { 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
protected void onDeviceDisconnected() {
mHRLocationCharacteristic = null;
mHRCharacteristic = null;
}
@Override
public void onCharacteristicNotified(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
if (mLogSession != null)
Logger.a(mLogSession, HeartRateMeasurementParser.parse(characteristic));
int hrValue; int hrValue;
//This will check if HR value is in 8 bits or 16 bits. if (isHeartRateInUINT16(characteristic.getValue()[0])) {
if (characteristic.getUuid().equals(HR_CHARACTERISTIC_UUID)) { hrValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1);
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);
}
}
@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();
} else { } 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 * This method will decode and return Heart rate sensor position on body
*/ */
private String getBodySensorPosition(byte bodySensorPositionValue) { private String getBodySensorPosition(final byte bodySensorPositionValue) {
String[] locations = mContext.getResources().getStringArray(R.array.hrs_locations); final String[] locations = getContext().getResources().getStringArray(R.array.hrs_locations);
if (bodySensorPositionValue > locations.length) if (bodySensorPositionValue > locations.length)
return mContext.getString(R.string.hrs_location_other); return getContext().getString(R.string.hrs_location_other);
return locations[bodySensorPositionValue]; return locations[bodySensorPositionValue];
} }
/** /**
* This method will check if Heart rate value is in 8 bits or 16 bits * This method will check if Heart rate value is in 8 bits or 16 bits
*/ */
private boolean isHeartRateInUINT16(byte value) { private boolean isHeartRateInUINT16(final byte value) {
return ((value & FIRST_BITMASK) != 0); return ((value & 0x01) != 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;
}
} }
} }

View File

@@ -25,11 +25,6 @@ import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
public interface HRSManagerCallbacks extends BleManagerCallbacks { 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 * Called when the sensor position information has been obtained from the sensor
* *

View File

@@ -21,6 +21,11 @@
*/ */
package no.nordicsemi.android.nrftoolbox.hrs; 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.ChartFactory;
import org.achartengine.GraphicalView; import org.achartengine.GraphicalView;
import org.achartengine.chart.PointStyle; import org.achartengine.chart.PointStyle;
@@ -29,11 +34,6 @@ import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.renderer.XYMultipleSeriesRenderer; import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer; 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 * This class uses external library AChartEngine to show dynamic real time line graph for HR values
*/ */

View File

@@ -21,15 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.hts; 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.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -41,6 +32,15 @@ import android.support.v4.content.LocalBroadcastManager;
import android.view.Menu; import android.view.Menu;
import android.widget.TextView; 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 * 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. * orientations.
@@ -136,6 +136,11 @@ public class HTSActivity extends BleProfileServiceReadyActivity<HTSService.RSCBi
// not used // not used
} }
@Override
protected int getLoggerProfileTitle() {
return R.string.hts_feature_title;
}
@Override @Override
protected int getAboutTextId() { protected int getAboutTextId() {
return R.string.hts_about_text; return R.string.hts_about_text;

View File

@@ -21,48 +21,33 @@
*/ */
package no.nordicsemi.android.nrftoolbox.hts; package no.nordicsemi.android.nrftoolbox.hts;
import java.util.List; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID; 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.profile.BleManager;
import no.nordicsemi.android.nrftoolbox.utility.DebugLogger; 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;
/** /**
* HTSManager class performs BluetoothGatt operations for connection, service discovery, enabling indication and reading characteristics. All operations required to connect to device with BLE HT * 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 * 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<HTSManagerCallbacks> { public class HTSManager extends BleManager<HTSManagerCallbacks> {
private final String TAG = "HTSManager"; private static final String TAG = "HTSManager";
private HTSManagerCallbacks mCallbacks;
private BluetoothGatt mBluetoothGatt;
private Context mContext;
/** Health Thermometer service UUID */
public final static UUID HT_SERVICE_UUID = UUID.fromString("00001809-0000-1000-8000-00805f9b34fb"); 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 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 BluetoothGattCharacteristic mHTCharacteristic;
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 final static int HIDE_MSB_8BITS_OUT_OF_32BITS = 0x00FFFFFF; private final static int HIDE_MSB_8BITS_OUT_OF_32BITS = 0x00FFFFFF;
private final static int HIDE_MSB_8BITS_OUT_OF_16BITS = 0x00FF; private final static int HIDE_MSB_8BITS_OUT_OF_16BITS = 0x00FF;
@@ -71,178 +56,55 @@ public class HTSManager implements BleManager<HTSManagerCallbacks> {
private final static int GET_BIT24 = 0x00400000; private final static int GET_BIT24 = 0x00400000;
private final static int FIRST_BIT_MASK = 0x01; private final static int FIRST_BIT_MASK = 0x01;
private static HTSManager managerInstance = null; public HTSManager(final Context context) {
super(context);
/**
* 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 @Override
public void connect(Context context, BluetoothDevice device) { protected BleManagerGattCallback getGattCallback() {
mBluetoothGatt = device.connectGatt(context, false, mGattCallback); return mGattCallback;
mContext = context;
} }
@Override
public void disconnect() {
DebugLogger.d(TAG, "Disconnecting device");
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
}
}
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 * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
*/ */
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() {
@Override @Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { protected Queue<Request> initGatt(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final LinkedList<Request> requests = new LinkedList<>();
if (newState == BluetoothProfile.STATE_CONNECTED) { requests.push(Request.newEnableIndicationsRequest(mHTCharacteristic));
DebugLogger.d(TAG, "Device connected"); return requests;
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);
}
} }
@Override @Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) { protected boolean isRequiredServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final BluetoothGattService service = gatt.getService(HT_SERVICE_UUID);
List<BluetoothGattService> services = gatt.getServices(); if (service != null) {
for (BluetoothGattService service : services) { mHTCharacteristic = service.getCharacteristic(HT_MEASUREMENT_CHARACTERISTIC_UUID);
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);
} }
return mHTCharacteristic != null;
} }
@Override @Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { protected void onDeviceDisconnected() {
if (status == BluetoothGatt.GATT_SUCCESS) { mHTCharacteristic = null;
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);
}
} }
@Override @Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { public void onCharacteristicIndicated(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
if (characteristic.getUuid().equals(HT_MEASUREMENT_CHARACTERISTIC_UUID)) { if (mLogSession != null)
try { Logger.a(mLogSession, TemperatureMeasurementParser.parse(characteristic));
double tempValue = decodeTemperature(characteristic.getValue());
mCallbacks.onHTValueReceived(tempValue);
} catch (Exception e) {
DebugLogger.e(TAG, "invalid temperature value");
}
}
}
@Override try {
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { final double tempValue = decodeTemperature(characteristic.getValue());
if (status == BluetoothGatt.GATT_SUCCESS) { mCallbacks.onHTValueReceived(tempValue);
readBatteryLevel(); } catch (Exception e) {
} else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { DebugLogger.e(TAG, "Invalid temperature value", e);
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 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 * 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 * 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 @@ public class HTSManager implements BleManager<HTSManagerCallbacks> {
int mantissa = ((thirdOctet << SHIFT_LEFT_16BITS) | (secondOctet << SHIFT_LEFT_8BITS) | (firstOctet)) & HIDE_MSB_8BITS_OUT_OF_32BITS; int mantissa = ((thirdOctet << SHIFT_LEFT_16BITS) | (secondOctet << SHIFT_LEFT_8BITS) | (firstOctet)) & HIDE_MSB_8BITS_OUT_OF_32BITS;
mantissa = getTwosComplimentOfNegativeMantissa(mantissa); mantissa = getTwosComplimentOfNegativeMantissa(mantissa);
temperatureValue = (mantissa * Math.pow(10, exponential)); temperatureValue = (mantissa * Math.pow(10, exponential));
/* /*
* Conversion of temperature unit from Fahrenheit to Celsius if unit is in Fahrenheit * Conversion of temperature unit from Fahrenheit to Celsius if unit is in Fahrenheit
* Celsius = (98.6*Fahrenheit -32) 5/9 * Celsius = (98.6*Fahrenheit -32) 5/9
@@ -282,19 +145,4 @@ public class HTSManager implements BleManager<HTSManagerCallbacks> {
return 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;
}
}
} }

View File

@@ -22,11 +22,6 @@
package no.nordicsemi.android.nrftoolbox.hts; 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.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@@ -34,9 +29,14 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager; 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 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 BROADCAST_HTS_MEASUREMENT = "no.nordicsemi.android.nrftoolbox.hts.BROADCAST_HTS_MEASUREMENT";
public static final String EXTRA_TEMPERATURE = "no.nordicsemi.android.nrftoolbox.hts.EXTRA_TEMPERATURE"; 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 final static int DISCONNECT_REQ = 1;
private HTSManager mManager; private HTSManager mManager;
private boolean mBinded;
private final LocalBinder mBinder = new RSCBinder(); 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 { public class RSCBinder extends LocalBinder {
// empty // empty
@@ -66,7 +65,7 @@ public class HTSService extends BleProfileService implements HTSManagerCallbacks
@Override @Override
protected BleManager<HTSManagerCallbacks> initializeManager() { protected BleManager<HTSManagerCallbacks> initializeManager() {
return mManager = HTSManager.getHTSManager(); return mManager = new HTSManager(this);
} }
@Override @Override
@@ -88,28 +87,21 @@ public class HTSService extends BleProfileService implements HTSManagerCallbacks
} }
@Override @Override
public IBinder onBind(final Intent intent) { protected void onRebind() {
mBinded = true;
return super.onBind(intent);
}
@Override
public void onRebind(final Intent intent) {
mBinded = true;
// when the activity rebinds to the service, remove the notification // when the activity rebinds to the service, remove the notification
cancelNotification(); cancelNotification();
// read the battery level when back in the Activity
if (isConnected())
mManager.readBatteryLevel();
} }
@Override @Override
public boolean onUnbind(final Intent intent) { protected void onUnbind() {
mBinded = false; // when the activity closes we need to show the notification that user is connected to the sensor
// 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);
createNotifcation(R.string.hts_notification_connected_message, 0); }
return super.onUnbind(intent);
@Override
protected void onServiceStarted() {
// logger is now available. Assign it to the manager
mManager.setLogger(getLogSession());
} }
@Override @Override
@@ -117,6 +109,11 @@ public class HTSService extends BleProfileService implements HTSManagerCallbacks
final Intent broadcast = new Intent(BROADCAST_HTS_MEASUREMENT); final Intent broadcast = new Intent(BROADCAST_HTS_MEASUREMENT);
broadcast.putExtra(EXTRA_TEMPERATURE, value); broadcast.putExtra(EXTRA_TEMPERATURE, value);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); 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 class HTSService extends BleProfileService implements HTSManagerCallbacks
* @param defaults * @param defaults
* signals that will be used to notify the user * 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); final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, HTSActivity.class); final Intent targetIntent = new Intent(this, HTSActivity.class);
@@ -163,7 +160,7 @@ public class HTSService extends BleProfileService implements HTSManagerCallbacks
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(final Context context, final Intent intent) { 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()) if (isConnected())
getBinder().disconnect(); getBinder().disconnect();
else else

View File

@@ -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 + ")";
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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 + ")";
}
}
}

View File

@@ -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();
}
}

View File

@@ -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<Float> 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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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";
}
}
}

View File

@@ -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();
}
}

View File

@@ -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";
}
}
}

View File

@@ -21,37 +21,845 @@
*/ */
package no.nordicsemi.android.nrftoolbox.profile; package no.nordicsemi.android.nrftoolbox.profile;
import android.app.Activity;
import android.bluetooth.BluetoothDevice; 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.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
public interface BleManager<E extends BleManagerCallbacks> { 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;
/**
* <p>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.</p>
* <p>The manager handles connection events and initializes the device after establishing the connection.
* <ol>
* <li>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.</li>
* <li>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.</li>
* <li>Afterwards, the manager initializes the device using given queue of commands. See {@link BleManagerGattCallback#initGatt(android.bluetooth.BluetoothGatt)} method for more details.</li>
* <li>When initialization complete, the {@link BleManagerCallbacks#onDeviceReady()} callback is called.</li>
* </ol>The manager also is responsible for parsing the Battery Level values and calling {@link BleManagerCallbacks#onBatteryValueReceived(int)} method.</p>
* <p>Events from all profiles are being logged into the nRF Logger application,
* which may be downloaded from Google Play: <a href="https://play.google.com/store/apps/details?id=no.nordicsemi.android.log">https://play.google.com/store/apps/details?id=no.nordicsemi.android.log</a></p>
* <p>The nRF Logger application allows you to see application logs without need to connect it to the computer.</p>
*
* @param <E> The profile callbacks type
*/
public abstract class BleManager<E extends BleManagerCallbacks> {
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 * Connects to the Bluetooth Smart device
* *
* @param context * @param device a device to connect to
* this must be an application context, not the Activity. Call {@link Activity#getApplicationContext()} to get one.
* @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. * Disconnects from the device. Does nothing if not connected.
*/ */
public void disconnect(); public void disconnect() {
mUserDisconnected = true;
/** if (mConnected && mBluetoothGatt != null) {
* Sets the manager callback listener Logger.v(mLogSession, "Disconnecting...");
* Logger.d(mLogSession, "gatt.disconnect()");
* @param callbacks mBluetoothGatt.disconnect();
* the callback listener }
*/ }
public void setGattCallbacks(E callbacks);
/** /**
* Closes and releases resources. May be also used to unregister broadcast listeners. * Closes and releases resources. May be also used to unregister broadcast listeners.
*/ */
public void closeBluetoothGatt(); 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
*/
public void setGattCallbacks(E callbacks) {
mCallbacks = callbacks;
}
/**
* 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 <code>false</code>.
*
* @param gatt the gatt device with services discovered
* @return <code>true</code> when the request has been sent, <code>false</code> 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 <code>null</code> 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 <code>null</code> 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
*/
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 <code>true</code> 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<Request> mInitQueue;
private boolean mInitInProgress;
/**
* This method should return <code>true</code> when the gatt device supports the required services.
*
* @param gatt the gatt device with services discovered
* @return <code>true</code> when the device has teh required service
*/
protected abstract boolean isRequiredServiceSupported(final BluetoothGatt gatt);
/**
* This method should return <code>true</code> when the gatt device supports the optional services.
* The default implementation returns <code>false</code>.
*
* @param gatt the gatt device with services discovered
* @return <code>true</code> 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.
* <p>This method is called when the services has been discovered and the device is supported (has required service).</p>
*
* @param gatt the gatt device with services discovered
* @return the queue of requests
*/
protected abstract Queue<Request> 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.
* <p/>
* <p>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<Request> 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";
}
}
}
} }

View File

@@ -32,6 +32,11 @@ public interface BleManagerCallbacks {
*/ */
public void onDeviceConnected(); 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. * 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); 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 * Called when battery value has been received from the device
* *

View File

@@ -21,18 +21,13 @@
*/ */
package no.nordicsemi.android.nrftoolbox.profile; 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.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.view.Menu; import android.view.Menu;
@@ -42,6 +37,16 @@ import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; 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 { public abstract class BleProfileActivity extends ActionBarActivity implements BleManagerCallbacks, ScannerFragment.OnDeviceSelectedListener {
private static final String TAG = "BaseProfileActivity"; private static final String TAG = "BaseProfileActivity";
@@ -54,6 +59,7 @@ public abstract class BleProfileActivity extends ActionBarActivity implements Bl
private TextView mDeviceNameView; private TextView mDeviceNameView;
private TextView mBatteryLevelView; private TextView mBatteryLevelView;
private Button mConnectButton; private Button mConnectButton;
private ILogSession mLogSession;
private boolean mDeviceConnected = false; private boolean mDeviceConnected = false;
private String mDeviceName; private String mDeviceName;
@@ -185,10 +191,38 @@ public abstract class BleProfileActivity extends ActionBarActivity implements Bl
} }
} }
/**
* 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 @Override
public void onDeviceSelected(final BluetoothDevice device, final String name) { 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); mDeviceNameView.setText(mDeviceName = name);
mBleManager.connect(getApplicationContext(), device); mConnectButton.setText(R.string.action_disconnect);
mBleManager.connect(device);
} }
@Override @Override
@@ -207,10 +241,15 @@ public abstract class BleProfileActivity extends ActionBarActivity implements Bl
}); });
} }
@Override
public void onDeviceDisconnecting() {
// do nothing
}
@Override @Override
public void onDeviceDisconnected() { public void onDeviceDisconnected() {
mDeviceConnected = false; mDeviceConnected = false;
mBleManager.closeBluetoothGatt(); mBleManager.close();
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -256,11 +295,8 @@ public abstract class BleProfileActivity extends ActionBarActivity implements Bl
@Override @Override
public void onError(final String message, final int errorCode) { 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 + ")"); showToast(message + " (" + errorCode + ")");
// refresh UI when connection failed
onDeviceDisconnected();
} }
@Override @Override

View File

@@ -27,6 +27,7 @@ import android.bluetooth.BluetoothManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@@ -37,6 +38,9 @@ import android.widget.Toast;
import java.util.UUID; 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.AppHelpFragment;
import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.R;
import no.nordicsemi.android.nrftoolbox.app.ExpandableListActivity; import no.nordicsemi.android.nrftoolbox.app.ExpandableListActivity;
@@ -55,6 +59,7 @@ public abstract class BleProfileExpandableListActivity extends ExpandableListAct
private TextView mDeviceNameView; private TextView mDeviceNameView;
private TextView mBatteryLevelView; private TextView mBatteryLevelView;
private Button mConnectButton; private Button mConnectButton;
private ILogSession mLogSession;
private boolean mDeviceConnected = false; private boolean mDeviceConnected = false;
private String mDeviceName; private String mDeviceName;
@@ -184,10 +189,38 @@ public abstract class BleProfileExpandableListActivity extends ExpandableListAct
} }
} }
/**
* 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 @Override
public void onDeviceSelected(final BluetoothDevice device, final String name) { 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); mDeviceNameView.setText(mDeviceName = name);
mBleManager.connect(getApplicationContext(), device); mConnectButton.setText(R.string.action_disconnect);
mBleManager.connect(device);
} }
@Override @Override
@@ -206,10 +239,15 @@ public abstract class BleProfileExpandableListActivity extends ExpandableListAct
}); });
} }
@Override
public void onDeviceDisconnecting() {
// do nothing
}
@Override @Override
public void onDeviceDisconnected() { public void onDeviceDisconnected() {
mDeviceConnected = false; mDeviceConnected = false;
mBleManager.closeBluetoothGatt(); mBleManager.close();
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -233,6 +271,18 @@ public abstract class BleProfileExpandableListActivity extends ExpandableListAct
}); });
} }
@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 @Override
public void onBatteryValueReceived(final int value) { public void onBatteryValueReceived(final int value) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@@ -255,11 +305,8 @@ public abstract class BleProfileExpandableListActivity extends ExpandableListAct
@Override @Override
public void onError(final String message, final int errorCode) { 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 + ")"); showToast(message + " (" + errorCode + ")");
// refresh UI when connection failed
onDeviceDisconnected();
} }
@Override @Override

View File

@@ -21,9 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.profile; 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.app.Service;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
@@ -36,12 +33,17 @@ import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast; 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 { public abstract class BleProfileService extends Service implements BleManagerCallbacks {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String TAG = "BleProfileService"; 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_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_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_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_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL";
public static final String BROADCAST_ERROR = "no.nordicsemi.android.nrftoolbox.BROADCAST_ERROR"; 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<BleManagerCallbacks> mBleManager; private BleManager<BleManagerCallbacks> mBleManager;
private Handler mHandler; private Handler mHandler;
protected boolean mBinded;
private boolean mActivityFinished;
private boolean mConnected; private boolean mConnected;
private String mDeviceAddress; private String mDeviceAddress;
private String mDeviceName; private String mDeviceName;
@@ -78,12 +82,14 @@ public abstract class BleProfileService extends Service implements BleManagerCal
* Disconnects from the sensor. * Disconnects from the sensor.
*/ */
public final void disconnect() { public final void disconnect() {
onDeviceDisconnecting();
if (!mConnected) { if (!mConnected) {
mBleManager.close();
onDeviceDisconnected(); onDeviceDisconnected();
return; 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); final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTING); broadcast.putExtra(EXTRA_CONNECTION_STATE, STATE_DISCONNECTING);
LocalBroadcastManager.getInstance(BleProfileService.this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(BleProfileService.this).sendBroadcast(broadcast);
@@ -91,6 +97,14 @@ public abstract class BleProfileService extends Service implements BleManagerCal
mBleManager.disconnect(); mBleManager.disconnect();
} }
/**
* Sets whether the binded activity if finishing or not. If <code>true</code>, 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 * Returns the device address
* *
@@ -129,14 +143,9 @@ public abstract class BleProfileService extends Service implements BleManagerCal
} }
} }
@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. * 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 * @return the service binder
*/ */
protected LocalBinder getBinder() { protected LocalBinder getBinder() {
@@ -145,11 +154,55 @@ public abstract class BleProfileService extends Service implements BleManagerCal
} }
@Override @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 // we must allow to rebind to the same service
return true; 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") @SuppressWarnings("unchecked")
@Override @Override
public void onCreate() { public void onCreate() {
@@ -187,8 +240,7 @@ public abstract class BleProfileService extends Service implements BleManagerCal
mDeviceName = device.getName(); mDeviceName = device.getName();
onServiceStarted(); onServiceStarted();
Logger.v(mLogSession, "Connecting..."); mBleManager.connect(device);
mBleManager.connect(BleProfileService.this, device);
return START_REDELIVER_INTENT; return START_REDELIVER_INTENT;
} }
@@ -204,7 +256,7 @@ public abstract class BleProfileService extends Service implements BleManagerCal
super.onDestroy(); super.onDestroy();
// shutdown the manager // shutdown the manager
mBleManager.closeBluetoothGatt(); mBleManager.close();
Logger.i(mLogSession, "Service destroyed"); Logger.i(mLogSession, "Service destroyed");
mBleManager = null; mBleManager = null;
mDeviceAddress = null; mDeviceAddress = null;
@@ -215,7 +267,6 @@ public abstract class BleProfileService extends Service implements BleManagerCal
@Override @Override
public void onDeviceConnected() { public void onDeviceConnected() {
Logger.i(mLogSession, "Connected to " + mDeviceAddress);
mConnected = true; mConnected = true;
final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE); final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
@@ -225,9 +276,13 @@ public abstract class BleProfileService extends Service implements BleManagerCal
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
} }
@Override
public void onDeviceDisconnecting() {
// do nothing
}
@Override @Override
public void onDeviceDisconnected() { public void onDeviceDisconnected() {
Logger.i(mLogSession, "Disconnected");
mConnected = false; mConnected = false;
mDeviceAddress = null; mDeviceAddress = null;
mDeviceName = null; mDeviceName = null;
@@ -243,7 +298,6 @@ public abstract class BleProfileService extends Service implements BleManagerCal
@Override @Override
public void onLinklossOccur() { public void onLinklossOccur() {
Logger.w(mLogSession, "Connection lost");
mConnected = false; mConnected = false;
final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE); final Intent broadcast = new Intent(BROADCAST_CONNECTION_STATE);
@@ -253,11 +307,6 @@ public abstract class BleProfileService extends Service implements BleManagerCal
@Override @Override
public void onServicesDiscovered(final boolean optionalServicesFound) { 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); final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED);
broadcast.putExtra(EXTRA_SERVICE_PRIMARY, true); broadcast.putExtra(EXTRA_SERVICE_PRIMARY, true);
broadcast.putExtra(EXTRA_SERVICE_SECONDARY, optionalServicesFound); broadcast.putExtra(EXTRA_SERVICE_SECONDARY, optionalServicesFound);
@@ -265,10 +314,13 @@ public abstract class BleProfileService extends Service implements BleManagerCal
} }
@Override @Override
public void onDeviceNotSupported() { public void onDeviceReady() {
Logger.i(mLogSession, "Services Discovered"); final Intent broadcast = new Intent(BROADCAST_DEVICE_READY);
Logger.w(mLogSession, "Device is not supported"); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
}
@Override
public void onDeviceNotSupported() {
final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED); final Intent broadcast = new Intent(BROADCAST_SERVICES_DISCOVERED);
broadcast.putExtra(EXTRA_SERVICE_PRIMARY, false); broadcast.putExtra(EXTRA_SERVICE_PRIMARY, false);
broadcast.putExtra(EXTRA_SERVICE_SECONDARY, false); broadcast.putExtra(EXTRA_SERVICE_SECONDARY, false);
@@ -279,8 +331,6 @@ public abstract class BleProfileService extends Service implements BleManagerCal
@Override @Override
public void onBatteryValueReceived(final int value) { public void onBatteryValueReceived(final int value) {
Logger.i(mLogSession, "Battery level received: " + value + "%");
final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL); final Intent broadcast = new Intent(BROADCAST_BATTERY_LEVEL);
broadcast.putExtra(EXTRA_BATTERY_LEVEL, value); broadcast.putExtra(EXTRA_BATTERY_LEVEL, value);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
@@ -288,7 +338,6 @@ public abstract class BleProfileService extends Service implements BleManagerCal
@Override @Override
public void onBondingRequired() { public void onBondingRequired() {
Logger.v(mLogSession, "Bond state: Bonding...");
showToast(R.string.bonding); showToast(R.string.bonding);
final Intent broadcast = new Intent(BROADCAST_BOND_STATE); final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
@@ -298,7 +347,6 @@ public abstract class BleProfileService extends Service implements BleManagerCal
@Override @Override
public void onBonded() { public void onBonded() {
Logger.i(mLogSession, "Bond state: Bonded");
showToast(R.string.bonded); showToast(R.string.bonded);
final Intent broadcast = new Intent(BROADCAST_BOND_STATE); final Intent broadcast = new Intent(BROADCAST_BOND_STATE);
@@ -308,8 +356,6 @@ public abstract class BleProfileService extends Service implements BleManagerCal
@Override @Override
public void onError(final String message, final int errorCode) { public void onError(final String message, final int errorCode) {
Logger.e(mLogSession, message + " (" + errorCode + ")");
final Intent broadcast = new Intent(BROADCAST_ERROR); final Intent broadcast = new Intent(BROADCAST_ERROR);
broadcast.putExtra(EXTRA_ERROR_MESSAGE, message); broadcast.putExtra(EXTRA_ERROR_MESSAGE, message);
broadcast.putExtra(EXTRA_ERROR_CODE, errorCode); broadcast.putExtra(EXTRA_ERROR_CODE, errorCode);

View File

@@ -68,7 +68,7 @@ import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
* </p> * </p>
*/ */
public abstract class BleProfileServiceReadyActivity<E extends BleProfileService.LocalBinder> extends ActionBarActivity implements public abstract class BleProfileServiceReadyActivity<E extends BleProfileService.LocalBinder> extends ActionBarActivity implements
ScannerFragment.OnDeviceSelectedListener { ScannerFragment.OnDeviceSelectedListener, BleManagerCallbacks {
private static final String TAG = "BleProfileServiceReadyActivity"; private static final String TAG = "BleProfileServiceReadyActivity";
private static final String DEVICE_NAME = "device_name"; private static final String DEVICE_NAME = "device_name";
@@ -89,59 +89,73 @@ public abstract class BleProfileServiceReadyActivity<E extends BleProfileService
public void onReceive(final Context context, final Intent intent) { public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction(); final String action = intent.getAction();
if (BleProfileService.BROADCAST_CONNECTION_STATE.equals(action)) { switch (action) {
final int state = intent.getIntExtra(BleProfileService.EXTRA_CONNECTION_STATE, BleProfileService.STATE_DISCONNECTED); case BleProfileService.BROADCAST_CONNECTION_STATE: {
final int state = intent.getIntExtra(BleProfileService.EXTRA_CONNECTION_STATE, BleProfileService.STATE_DISCONNECTED);
switch (state) { switch (state) {
case BleProfileService.STATE_CONNECTED: { case BleProfileService.STATE_CONNECTED: {
mDeviceName = intent.getStringExtra(BleProfileService.EXTRA_DEVICE_NAME); mDeviceName = intent.getStringExtra(BleProfileService.EXTRA_DEVICE_NAME);
onDeviceConnected(); onDeviceConnected();
break; break;
}
case BleProfileService.STATE_DISCONNECTED: {
onDeviceDisconnected();
mDeviceName = null;
break;
}
case BleProfileService.STATE_LINK_LOSS: {
onLinklossOccur();
break;
}
case BleProfileService.STATE_CONNECTING:
case BleProfileService.STATE_DISCONNECTING:
// current implementation does nothing in this states
default:
// there should be no other actions
break;
} }
case BleProfileService.STATE_DISCONNECTED: { break;
onDeviceDisconnected(); }
mDeviceName = null; case BleProfileService.BROADCAST_SERVICES_DISCOVERED: {
break; final boolean primaryService = intent.getBooleanExtra(BleProfileService.EXTRA_SERVICE_PRIMARY, false);
} final boolean secondaryService = intent.getBooleanExtra(BleProfileService.EXTRA_SERVICE_SECONDARY, false);
case BleProfileService.STATE_LINK_LOSS: {
onLinklossOccur();
break;
}
case BleProfileService.STATE_CONNECTING:
case BleProfileService.STATE_DISCONNECTING:
// current implementation does nothing in this states
default:
// there should be no other actions
break;
if (primaryService) {
onServicesDiscovered(secondaryService);
} else {
onDeviceNotSupported();
}
break;
} }
} else if (BleProfileService.BROADCAST_SERVICES_DISCOVERED.equals(action)) { case BleProfileService.BROADCAST_DEVICE_READY: {
final boolean primaryService = intent.getBooleanExtra(BleProfileService.EXTRA_SERVICE_PRIMARY, false); onDeviceReady();
final boolean secondaryService = intent.getBooleanExtra(BleProfileService.EXTRA_SERVICE_SECONDARY, false); break;
if (primaryService) {
onServicesDiscovered(secondaryService);
} else {
onDeviceNotSupported();
} }
} else if (BleProfileService.BROADCAST_BOND_STATE.equals(action)) { case BleProfileService.BROADCAST_BOND_STATE: {
final int state = intent.getIntExtra(BleProfileService.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); final int state = intent.getIntExtra(BleProfileService.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
switch (state) { switch (state) {
case BluetoothDevice.BOND_BONDING: case BluetoothDevice.BOND_BONDING:
onBondingRequired(); onBondingRequired();
break; break;
case BluetoothDevice.BOND_BONDED: case BluetoothDevice.BOND_BONDED:
onBonded(); onBonded();
break; break;
}
break;
}
case BleProfileService.BROADCAST_BATTERY_LEVEL: {
final int value = intent.getIntExtra(BleProfileService.EXTRA_BATTERY_LEVEL, -1);
if (value > 0)
onBatteryValueReceived(value);
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 @@ public abstract class BleProfileServiceReadyActivity<E extends BleProfileService
super.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. * notified via mServiceConnection.
*/ */
final Intent service = new Intent(this, getServiceClass()); final Intent service = new Intent(this, getServiceClass());
@@ -217,8 +231,8 @@ public abstract class BleProfileServiceReadyActivity<E extends BleProfileService
Logger.d(mLogSession, "Binding to the service..."); // (* - see the comment below) 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 * * - 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. * 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 @@ public abstract class BleProfileServiceReadyActivity<E extends BleProfileService
super.onStop(); super.onStop();
try { 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..."); Logger.d(mLogSession, "Unbinding from the service...");
unbindService(mServiceConnection); unbindService(mServiceConnection);
mService = null; mService = null;
@@ -251,6 +270,7 @@ public abstract class BleProfileServiceReadyActivity<E extends BleProfileService
final IntentFilter intentFilter = new IntentFilter(); final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BleProfileService.BROADCAST_CONNECTION_STATE); intentFilter.addAction(BleProfileService.BROADCAST_CONNECTION_STATE);
intentFilter.addAction(BleProfileService.BROADCAST_SERVICES_DISCOVERED); intentFilter.addAction(BleProfileService.BROADCAST_SERVICES_DISCOVERED);
intentFilter.addAction(BleProfileService.BROADCAST_DEVICE_READY);
intentFilter.addAction(BleProfileService.BROADCAST_BOND_STATE); intentFilter.addAction(BleProfileService.BROADCAST_BOND_STATE);
intentFilter.addAction(BleProfileService.BROADCAST_BATTERY_LEVEL); intentFilter.addAction(BleProfileService.BROADCAST_BATTERY_LEVEL);
intentFilter.addAction(BleProfileService.BROADCAST_ERROR); intentFilter.addAction(BleProfileService.BROADCAST_ERROR);
@@ -282,7 +302,7 @@ public abstract class BleProfileServiceReadyActivity<E extends BleProfileService
* *
* @return the service binder or <code>null</code> * @return the service binder or <code>null</code>
*/ */
protected BleProfileService.LocalBinder getService() { protected E getService() {
return mService; return mService;
} }
@@ -372,6 +392,7 @@ public abstract class BleProfileServiceReadyActivity<E extends BleProfileService
showDeviceScanningDialog(getFilterUUID(), isDiscoverableRequired()); showDeviceScanningDialog(getFilterUUID(), isDiscoverableRequired());
} else { } else {
Logger.v(mLogSession, "Disconnecting..."); Logger.v(mLogSession, "Disconnecting...");
onDeviceDisconnecting();
mService.disconnect(); mService.disconnect();
} }
} else { } else {
@@ -436,6 +457,11 @@ public abstract class BleProfileServiceReadyActivity<E extends BleProfileService
mConnectButton.setText(R.string.action_disconnect); mConnectButton.setText(R.string.action_disconnect);
} }
@Override
public void onDeviceDisconnecting() {
// do nothing
}
/** /**
* Called when the device has disconnected (when the callback returned * Called when the device has disconnected (when the callback returned
* {@link BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} with state DISCONNECTED. * {@link BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} with state DISCONNECTED.
@@ -478,6 +504,13 @@ public abstract class BleProfileServiceReadyActivity<E extends BleProfileService
*/ */
public abstract void onServicesDiscovered(final boolean optionalServicesFound); 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 * Called when the device has started bonding process
*/ */
@@ -517,11 +550,8 @@ public abstract class BleProfileServiceReadyActivity<E extends BleProfileService
* @param errorCode the error code * @param errorCode the error code
*/ */
public void onError(final String message, final int errorCode) { 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 + ")"); showToast(message + " (" + errorCode + ")");
// refresh UI when connection failed
onDeviceDisconnected();
} }
/** /**

View File

@@ -21,12 +21,13 @@
*/ */
package no.nordicsemi.android.nrftoolbox.proximity; package no.nordicsemi.android.nrftoolbox.proximity;
import no.nordicsemi.android.nrftoolbox.R;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.os.Bundle; import android.os.Bundle;
import no.nordicsemi.android.nrftoolbox.R;
public class LinklossFragment extends DialogFragment { public class LinklossFragment extends DialogFragment {
private static final String ARG_NAME = "name"; private static final String ARG_NAME = "name";

View File

@@ -21,12 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.proximity; 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.app.FragmentManager;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
@@ -37,12 +31,17 @@ import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.ImageView; 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<ProximityService.ProximityBinder> { public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityService.ProximityBinder> {
private static final String TAG = "ProximityActivity"; private static final String TAG = "ProximityActivity";
public static final String PREFS_GATT_SERVER_ENABLED = "prefs_gatt_server_enabled"; 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 Button mFindMeButton;
private ImageView mLockImage; private ImageView mLockImage;
@@ -64,31 +63,11 @@ public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityS
mGattServerSwitch.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() { mGattServerSwitch.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { 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);
}
@Override
protected void onRestoreInstanceState(final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
isImmediateAlertOn = savedInstanceState.getBoolean(IMMEDIATE_ALERT_STATUS);
if (isDeviceConnected()) {
showOpenLock();
if (isImmediateAlertOn) {
showSilentMeOnButton();
}
}
}
@Override @Override
protected int getLoggerProfileTitle() { protected int getLoggerProfileTitle() {
return R.string.proximity_feature_title; return R.string.proximity_feature_title;
@@ -96,7 +75,15 @@ public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityS
@Override @Override
protected void onServiceBinded(final ProximityService.ProximityBinder binder) { protected void onServiceBinded(final ProximityService.ProximityBinder binder) {
// you may get the binder instance here mGattServerSwitch.setEnabled(false);
if (binder.isConnected()) {
showOpenLock();
if (binder.isImmediateAlertOn()) {
showSilentMeOnButton();
}
}
} }
@Override @Override
@@ -131,14 +118,10 @@ public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityS
if (isBLEEnabled()) { if (isBLEEnabled()) {
if (!isDeviceConnected()) { if (!isDeviceConnected()) {
// do nothing // do nothing
} else if (!isImmediateAlertOn) { } else if (getService().toggleImmediateAlert()) {
showSilentMeOnButton(); showSilentMeOnButton();
((ProximityService.ProximityBinder) getService()).startImmediateAlert();
isImmediateAlertOn = true;
} else { } else {
showFindMeOnButton(); showFindMeOnButton();
((ProximityService.ProximityBinder) getService()).stopImmediateAlert();
isImmediateAlertOn = false;
} }
} else { } else {
showBLEDialog(); showBLEDialog();
@@ -147,13 +130,8 @@ public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityS
@Override @Override
protected void setDefaultUI() { protected void setDefaultUI() {
runOnUiThread(new Runnable() { mFindMeButton.setText(R.string.proximity_action_findme);
@Override mLockImage.setImageResource(R.drawable.proximity_lock_closed);
public void run() {
mFindMeButton.setText(R.string.proximity_action_findme);
mLockImage.setImageResource(R.drawable.proximity_lock_closed);
}
});
} }
@Override @Override
@@ -162,10 +140,8 @@ public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityS
} }
@Override @Override
public void onDeviceConnected() { public void onDeviceReady() {
super.onDeviceConnected();
showOpenLock(); showOpenLock();
mGattServerSwitch.setEnabled(false);
} }
@Override @Override
@@ -202,7 +178,6 @@ public class ProximityActivity extends BleProfileServiceReadyActivity<ProximityS
} }
private void resetForLinkloss() { private void resetForLinkloss() {
isImmediateAlertOn = false;
setDefaultUI(); setDefaultUI();
} }

View File

@@ -21,78 +21,56 @@
*/ */
package no.nordicsemi.android.nrftoolbox.proximity; 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.BluetoothDevice;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback; import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences; 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.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
public class ProximityManager implements BleManager<ProximityManagerCallbacks> { import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;
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 class ProximityManager extends BleManager<ProximityManagerCallbacks> {
private final String TAG = "ProximityManager"; private final String TAG = "ProximityManager";
private ProximityManagerCallbacks mCallbacks; /** Immediate Alert service UUID */
private BluetoothGattServer mBluetoothGattServer; public final static UUID IMMEDIATE_ALERT_SERVICE_UUID = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb");
private BluetoothGatt mBluetoothGatt; /** Linkloss service UUID */
private BluetoothDevice mDeviceToConnect;
private Context mContext;
private final Handler mHandler;
private ILogSession mLogSession;
private Ringtone mRingtoneNotification;
private Ringtone mRingtoneAlarm;
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 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 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 byte[] HIGH_ALERT = { 0x02 };
private final static UUID BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb"); private final static byte[] NO_ALERT = { 0x00 };
private final static String ERROR_CONNECTION_STATE_CHANGE = "Error on connection state change"; private BluetoothGattCharacteristic mAlertLevelCharacteristic, mLinklossCharacteristic;
private final static String ERROR_DISCOVERY_SERVICE = "Error on discovering services"; private BluetoothGattServer mBluetoothGattServer;
private final static String ERROR_AUTH_ERROR_WHILE_BONDED = "Phone has lost bonding information"; private BluetoothDevice mDeviceToConnect;
private final static String ERROR_WRITE_CHARACTERISTIC = "Error on writing characteristic"; private Handler mHandler;
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 boolean userDisconnectedFlag = false;
public ProximityManager(Context context) { public ProximityManager(Context context) {
initializeAlarm(context); super(context);
mHandler = new Handler(); mHandler = new Handler();
}
// Register bonding broadcast receiver @Override
final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); protected boolean shouldAutoConnect() {
context.registerReceiver(mBondingBroadcastReceiver, filter); return true;
} }
private void openGattServer(Context context, BluetoothManager manager) { private void openGattServer(Context context, BluetoothManager manager) {
@@ -111,10 +89,10 @@ public class ProximityManager implements BleManager<ProximityManagerCallbacks> {
/* /*
* 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. * 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); BluetoothGattCharacteristic.PERMISSION_WRITE);
alertLevel.setValue(HIGH_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0); alertLevel.setValue(HIGH_ALERT);
BluetoothGattService immediateAlertService = new BluetoothGattService(IMMEIDIATE_ALERT_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); final BluetoothGattService immediateAlertService = new BluetoothGattService(IMMEDIATE_ALERT_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
immediateAlertService.addCharacteristic(alertLevel); immediateAlertService.addCharacteristic(alertLevel);
mBluetoothGattServer.addService(immediateAlertService); mBluetoothGattServer.addService(immediateAlertService);
} }
@@ -123,347 +101,209 @@ public class ProximityManager implements BleManager<ProximityManagerCallbacks> {
/* /*
* 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. * 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); | BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_WRITE);
linklossAlertLevel.setValue(HIGH_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0); linklossAlertLevel.setValue(HIGH_ALERT);
BluetoothGattService linklossService = new BluetoothGattService(LINKLOSS_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); final BluetoothGattService linklossService = new BluetoothGattService(LINKLOSS_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
linklossService.addCharacteristic(linklossAlertLevel); linklossService.addCharacteristic(linklossAlertLevel);
mBluetoothGattServer.addService(linklossService); mBluetoothGattServer.addService(linklossService);
} }
private final BluetoothGattServerCallback mGattServerCallbacks = new BluetoothGattServerCallback() { 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());
}
@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();
} else {
Logger.i(mLogSession, "[Proximity Server] Immediate alarm request received: OFF");
stopAlarm();
}
}
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
DebugLogger.d(TAG, "[Proximity Server] onConnectionStateChange " + device.getName() + " status: " + status + " new state: " + newState);
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
DebugLogger.d(TAG, "[Proximity Server] onDescriptorReadRequest " + device.getName());
}
@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());
}
@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
DebugLogger.d(TAG, "[Proximity Server] onExecuteWrite " + device.getName());
}
@Override @Override
public void onServiceAdded(final int status, final BluetoothGattService service) { public void onServiceAdded(final int status, final BluetoothGattService service) {
DebugLogger.d(TAG, "[Proximity Server] onServiceAdded " + service.getUuid()); Logger.v(mLogSession, "[Server] Service " + service.getUuid() + " added");
mHandler.post(new Runnable() { mHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
// adding another service from callback thread fails on Samsung S4 with Android 4.3 // Adding another service from callback thread fails on Samsung S4 with Android 4.3
if (IMMEIDIATE_ALERT_SERVICE_UUID.equals(service.getUuid())) if (IMMEDIATE_ALERT_SERVICE_UUID.equals(service.getUuid()))
addLinklossService(); addLinklossService();
else { else {
DebugLogger.d(TAG, "[Proximity Server] Gatt server started");
Logger.i(mLogSession, "[Proximity Server] Gatt server started"); Logger.i(mLogSession, "[Proximity Server] Gatt server started");
if (mBluetoothGatt == null) { ProximityManager.super.connect(mDeviceToConnect);
mBluetoothGatt = mDeviceToConnect.connectGatt(mContext, false, mGattCallback); mDeviceToConnect = null;
mDeviceToConnect = null;
} else {
mBluetoothGatt.connect();
}
} }
} }
}); });
} }
@Override
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 {
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 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 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 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 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 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 @Override
public void setGattCallbacks(ProximityManagerCallbacks callbacks) { public void connect(final BluetoothDevice device) {
mCallbacks = callbacks; // Should we use the GATT Server?
} final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
final boolean useGattServer = preferences.getBoolean(ProximityActivity.PREFS_GATT_SERVER_ENABLED, true);
/** if (useGattServer) {
* Sets the log session that can be used to log events // Save the device that we want to connect to. First we will create a GATT Server
* mDeviceToConnect = device;
* @param logSession
*/
public void setLogger(ILogSession logSession) {
mLogSession = logSession;
}
@Override final BluetoothManager bluetoothManager = (BluetoothManager) getContext().getSystemService(Context.BLUETOOTH_SERVICE);
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);
try { try {
DebugLogger.d(TAG, "[Proximity Server] Starting Gatt server..."); DebugLogger.d(TAG, "[Server] Starting Gatt server...");
Logger.v(mLogSession, "[Proximity Server] Starting Gatt server..."); Logger.v(mLogSession, "[Server] Starting Gatt server...");
openGattServer(context, bluetoothManager); openGattServer(getContext(), bluetoothManager);
addImmediateAlertService(); addImmediateAlertService();
// the BluetoothGattServerCallback#onServiceAdded callback will proceed further operations // the BluetoothGattServerCallback#onServiceAdded callback will proceed further operations
} catch (final Exception e) { } 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. // 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); Log.e(TAG, "Creating Gatt Server failed", e);
} }
} else { } else {
if (mBluetoothGatt == null) { super.connect(device);
mBluetoothGatt = mDeviceToConnect.connectGatt(context, false, mGattCallback);
mDeviceToConnect = null;
} else {
mBluetoothGatt.connect();
}
} }
} }
@Override @Override
public void disconnect() { public void disconnect() {
if (mBluetoothGatt != null) { super.disconnect();
userDisconnectedFlag = true; closeGattServer();
mBluetoothGatt.disconnect();
stopAlarm();
closeGattServer();
}
} }
private void initializeAlarm(Context context) { @Override
final Uri alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); protected BleManagerGattCallback getGattCallback() {
mRingtoneAlarm = RingtoneManager.getRingtone(context, alarmUri); return mGattCallback;
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();
}
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();
} }
/** /**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
*/ */
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() {
@Override @Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { protected Queue<Request> initGatt(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final LinkedList<Request> requests = new LinkedList<>();
if (newState == BluetoothProfile.STATE_CONNECTED) { requests.push(Request.newWriteRequest(mLinklossCharacteristic, HIGH_ALERT));
DebugLogger.d(TAG, "Device connected"); return requests;
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);
}
} }
@Override @Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) { protected boolean isRequiredServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final BluetoothGattService llService = gatt.getService(LINKLOSS_SERVICE_UUID);
final List<BluetoothGattService> services = gatt.getServices(); if (llService != null) {
for (BluetoothGattService service : services) { mLinklossCharacteristic = llService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID);
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);
} }
return mLinklossCharacteristic != null;
} }
@Override @Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { protected boolean isOptionalServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final BluetoothGattService iaService = gatt.getService(IMMEDIATE_ALERT_SERVICE_UUID);
if (characteristic.getUuid().equals(BATTERY_LEVEL_CHARACTERISTIC_UUID)) { if (iaService != null) {
int batteryValue = characteristic.getValue()[0]; mAlertLevelCharacteristic = iaService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID);
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);
} }
return mAlertLevelCharacteristic != null;
} }
@Override @Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { protected void onDeviceDisconnected() {
if (status == BluetoothGatt.GATT_SUCCESS) { mAlertLevelCharacteristic = null;
if (characteristic.getUuid().equals(ALERT_LEVEL_CHARACTERISTIC_UUID)) { mLinklossCharacteristic = null;
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);
}
} }
}; };
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();
}
}
};
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() { public void writeImmediateAlertOn() {
Logger.a(mLogSession, "Immediate alarm request: ON");
if (mAlertLevelCharacteristic != null) { if (mAlertLevelCharacteristic != null) {
DebugLogger.d(TAG, "writing Immediate alert characteristic On"); mAlertLevelCharacteristic.setValue(HIGH_ALERT);
mAlertLevelCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); writeCharacteristic(mAlertLevelCharacteristic);
mAlertLevelCharacteristic.setValue(HIGH_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
mBluetoothGatt.writeCharacteristic(mAlertLevelCharacteristic);
} else { } else {
DebugLogger.w(TAG, "Immediate Alert Level Characteristic is not found"); DebugLogger.w(TAG, "Immediate Alert Level Characteristic is not found");
} }
} }
public void writeImmediateAlertOff() { public void writeImmediateAlertOff() {
Logger.a(mLogSession, "Immediate alarm request: OFF");
if (mAlertLevelCharacteristic != null) { if (mAlertLevelCharacteristic != null) {
DebugLogger.d(TAG, "writing Immediate alert characteristic Off"); mAlertLevelCharacteristic.setValue(NO_ALERT);
mAlertLevelCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); writeCharacteristic(mAlertLevelCharacteristic);
mAlertLevelCharacteristic.setValue(NO_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
mBluetoothGatt.writeCharacteristic(mAlertLevelCharacteristic);
} else { } else {
DebugLogger.w(TAG, "Immediate Alert Level Characteristic is not found"); DebugLogger.w(TAG, "Immediate Alert Level Characteristic is not found");
} }
} }
@Override @Override
public void closeBluetoothGatt() { public void close() {
try { super.close();
mContext.unregisterReceiver(mBondingBroadcastReceiver);
} catch (Exception e) {
// the receiver must have been not registered or unregistered before
}
if (mBluetoothGatt != null) {
mBluetoothGatt.close();
mBluetoothGatt = null;
}
if (mBluetoothGattServer != null) { if (mBluetoothGattServer != null) {
mBluetoothGattServer.close(); mBluetoothGattServer.close();
mBluetoothGattServer = null; mBluetoothGattServer = null;
} }
mCallbacks = null;
mLogSession = null;
mRingtoneAlarm = mRingtoneNotification = null;
} }
} }

View File

@@ -24,5 +24,7 @@ package no.nordicsemi.android.nrftoolbox.proximity;
import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks; import no.nordicsemi.android.nrftoolbox.profile.BleManagerCallbacks;
public interface ProximityManagerCallbacks extends BleManagerCallbacks { public interface ProximityManagerCallbacks extends BleManagerCallbacks {
// no additional methods public void onAlarmTriggered();
public void onAlarmStopped();
} }

View File

@@ -21,11 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.proximity; 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.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@@ -33,35 +28,53 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; 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 { public class ProximityService extends BleProfileService implements ProximityManagerCallbacks {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String TAG = "ProximityService"; private static final String TAG = "ProximityService";
private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.proximity.ACTION_DISCONNECT"; 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 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 NOTIFICATION_ID = 100;
private final static int OPEN_ACTIVITY_REQ = 0; private final static int OPEN_ACTIVITY_REQ = 0;
private final static int DISCONNECT_REQ = 1; 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(); 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 class ProximityBinder extends LocalBinder {
public void startImmediateAlert() { public boolean toggleImmediateAlert() {
Logger.i(getLogSession(), "[Proximity] Immediate alarm request: ON"); if (isImmediateAlertOn) {
mProximityManager.writeImmediateAlertOn(); stopImmediateAlert();
} else {
startImmediateAlert();
}
return isImmediateAlertOn; // this value is changed by methods above
} }
public void stopImmediateAlert() { public boolean isImmediateAlertOn() {
Logger.i(getLogSession(), "[Proximity] Immediate alarm request: OFF"); return isImmediateAlertOn;
mProximityManager.writeImmediateAlertOff();
} }
} }
@@ -75,43 +88,76 @@ public class ProximityService extends BleProfileService implements ProximityMana
return mProximityManager = new ProximityManager(this); 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 @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
final IntentFilter filter = new IntentFilter(); initializeAlarm();
filter.addAction(ACTION_DISCONNECT);
registerReceiver(mDisconnectActionBroadcastReceiver, filter); registerReceiver(mDisconnectActionBroadcastReceiver, new IntentFilter(ACTION_DISCONNECT));
registerReceiver(mFindMeActionBroadcastReceiver, new IntentFilter(ACTION_FIND_ME));
registerReceiver(mSilentMeActionBroadcastReceiver, new IntentFilter(ACTION_SILENT_ME));
} }
@Override @Override
public void onDestroy() { 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(); cancelNotification();
unregisterReceiver(mDisconnectActionBroadcastReceiver); unregisterReceiver(mDisconnectActionBroadcastReceiver);
unregisterReceiver(mFindMeActionBroadcastReceiver);
unregisterReceiver(mSilentMeActionBroadcastReceiver);
super.onDestroy(); super.onDestroy();
} }
@Override @Override
public IBinder onBind(final Intent intent) { protected void onRebind() {
mBinded = true;
return super.onBind(intent);
}
@Override
public void onRebind(final Intent intent) {
mBinded = true;
// when the activity rebinds to the service, remove the notification // when the activity rebinds to the service, remove the notification
cancelNotification(); cancelNotification();
} }
@Override @Override
public boolean onUnbind(final Intent intent) { public void onUnbind() {
mBinded = false; // when the activity closes we need to show the notification that user is connected to the sensor
// when the activity closes we need to show the notification that user is connected to the sensor if (isConnected())
createNotifcation(R.string.proximity_notification_connected_message, 0); createNotification(R.string.proximity_notification_connected_message, 0);
return super.onUnbind(intent); else
createNotification(R.string.proximity_notification_linkloss_alert, 0);
} }
@Override @Override
@@ -120,16 +166,39 @@ public class ProximityService extends BleProfileService implements ProximityMana
mProximityManager.setLogger(getLogSession()); mProximityManager.setLogger(getLogSession());
} }
@Override
public void onDeviceDisconnecting() {
stopAlarm();
}
@Override
public void onDeviceDisconnected() {
super.onDeviceDisconnected();
isImmediateAlertOn = false;
}
@Override @Override
public void onLinklossOccur() { public void onLinklossOccur() {
super.onLinklossOccur(); super.onLinklossOccur();
isImmediateAlertOn = false;
if (!mBinded) { if (!mBinded) {
// when the activity closes we need to show the notification that user is connected to the sensor // 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); playNotification();
createNotification(R.string.proximity_notification_linkloss_alert, Notification.DEFAULT_ALL);
} }
} }
@Override
public void onAlarmTriggered() {
playAlarm();
}
@Override
public void onAlarmStopped() {
stopAlarm();
}
/** /**
* Creates the notification * Creates the notification
* *
@@ -139,7 +208,7 @@ public class ProximityService extends BleProfileService implements ProximityMana
* @param defaults * @param defaults
* signals that will be used to notify the user * 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); final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, ProximityActivity.class); final Intent targetIntent = new Intent(this, ProximityActivity.class);
@@ -147,6 +216,15 @@ public class ProximityService extends BleProfileService implements ProximityMana
final Intent disconnect = new Intent(ACTION_DISCONNECT); final Intent disconnect = new Intent(ACTION_DISCONNECT);
final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT); 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 // 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 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); final Notification.Builder builder = new Notification.Builder(this).setContentIntent(pendingIntent);
@@ -154,6 +232,8 @@ public class ProximityService extends BleProfileService implements ProximityMana
builder.setSmallIcon(R.drawable.ic_stat_notify_proximity); builder.setSmallIcon(R.drawable.ic_stat_notify_proximity);
builder.setShowWhen(defaults != 0).setDefaults(defaults).setAutoCancel(true).setOngoing(true); 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); 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 Notification notification = builder.build();
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
@@ -168,17 +248,43 @@ public class ProximityService extends BleProfileService implements ProximityMana
nm.cancel(NOTIFICATION_ID); nm.cancel(NOTIFICATION_ID);
} }
/** private void initializeAlarm() {
* This broadcast receiver listens for {@link #ACTION_DISCONNECT} that may be fired by pressing Disconnect action button on the notification. final Uri alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
*/ mRingtoneAlarm = RingtoneManager.getRingtone(this, alarmUri);
private BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override final Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
public void onReceive(final Context context, final Intent intent) { mRingtoneNotification = RingtoneManager.getRingtone(this, notification);
Logger.i(getLogSession(), "[Proximity] Disconnect action pressed"); }
if (isConnected())
getBinder().disconnect(); private void playNotification() {
else mRingtoneNotification.play();
stopSelf(); }
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);
}
}
} }

View File

@@ -22,13 +22,6 @@
package no.nordicsemi.android.nrftoolbox.rsc; 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.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -40,6 +33,14 @@ import android.support.v4.content.LocalBroadcastManager;
import android.view.Menu; import android.view.Menu;
import android.widget.TextView; 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<RSCService.RSCBinder> { public class RSCActivity extends BleProfileServiceReadyActivity<RSCService.RSCBinder> {
private TextView mSpeedView; private TextView mSpeedView;
private TextView mSpeedUnitView; private TextView mSpeedUnitView;

View File

@@ -22,165 +22,71 @@
package no.nordicsemi.android.nrftoolbox.rsc; package no.nordicsemi.android.nrftoolbox.rsc;
import java.util.List; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import no.nordicsemi.android.log.ILogSession;
import no.nordicsemi.android.log.Logger; import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.parser.RSCMeasurementParser;
import no.nordicsemi.android.nrftoolbox.profile.BleManager; 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<RSCManagerCallbacks> {
private static final String TAG = "RSCManager";
private RSCManagerCallbacks mCallbacks;
private BluetoothGatt mBluetoothGatt;
private Context mContext;
private ILogSession mLogSession;
public class RSCManager extends BleManager<RSCManagerCallbacks> {
private static final byte INSTANTANEOUS_STRIDE_LENGTH_PRESENT = 0x01; // 1 bit 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 TOTAL_DISTANCE_PRESENT = 0x02; // 1 bit
private static final byte WALKING_OR_RUNNING_STATUS_BITS = 0x04; // 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"); 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 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"); private BluetoothGattCharacteristic mRSCMeasurementCharacteristic;
/** 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;
public RSCManager(final Context context) { public RSCManager(final Context context) {
// Register bonding broadcast receiver super(context);
final IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
context.registerReceiver(mBondingBroadcastReceiver, filter);
} }
@Override @Override
public void setGattCallbacks(final RSCManagerCallbacks callbacks) { protected BleManagerGattCallback getGattCallback() {
mCallbacks = callbacks; return mGattCallback;
}
public void setLogger(final ILogSession session) {
mLogSession = session;
}
@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();
}
} }
/** /**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
*/ */
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() {
@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. @Override
mCallbacks.onDeviceDisconnected(); protected Queue<Request> initGatt(final BluetoothGatt gatt) {
closeBluetoothGatt(); final LinkedList<Request> requests = new LinkedList<>();
} requests.push(Request.newEnableNotificationsRequest(mRSCMeasurementCharacteristic));
} else { return requests;
mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
}
} }
@Override @Override
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) { public boolean isRequiredServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final BluetoothGattService service = gatt.getService(RUNNING_SPEED_AND_CADENCE_SERVICE_UUID);
final List<BluetoothGattService> services = gatt.getServices(); if (service != null) {
for (BluetoothGattService service : services) { mRSCMeasurementCharacteristic = service.getCharacteristic(RSC_MEASUREMENT_CHARACTERISTIC_UUID);
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);
} }
return mRSCMeasurementCharacteristic != null;
} }
@Override @Override
public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { protected void onDeviceDisconnected() {
if (status == BluetoothGatt.GATT_SUCCESS) { mRSCMeasurementCharacteristic = null;
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);
}
} }
@Override @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 // Decode the new data
int offset = 0; int offset = 0;
final int flags = characteristic.getValue()[offset]; // 1 byte final int flags = characteristic.getValue()[offset]; // 1 byte
@@ -213,67 +119,4 @@ public class RSCManager implements BleManager<RSCManagerCallbacks> {
: RSCManagerCallbacks.ACTIVITY_WALKING); : 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;
}
} }

View File

@@ -22,11 +22,6 @@
package no.nordicsemi.android.nrftoolbox.rsc; 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.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@@ -35,9 +30,14 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager; 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 { public class RSCService extends BleProfileService implements RSCManagerCallbacks {
private static final String TAG = "RSCService"; 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 final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.rsc.ACTION_DISCONNECT";
private RSCManager mManager; private RSCManager mManager;
private boolean mBinded;
/** The last value of a cadence */ /** The last value of a cadence */
private float mCadence; private float mCadence;
@@ -109,28 +108,15 @@ public class RSCService extends BleProfileService implements RSCManagerCallbacks
} }
@Override @Override
public IBinder onBind(final Intent intent) { protected void onRebind() {
mBinded = true;
return super.onBind(intent);
}
@Override
public void onRebind(final Intent intent) {
mBinded = true;
// when the activity rebinds to the service, remove the notification // when the activity rebinds to the service, remove the notification
cancelNotification(); cancelNotification();
// read the battery level when back in the Activity
if (isConnected())
mManager.readBatteryLevel();
} }
@Override @Override
public boolean onUnbind(final Intent intent) { protected void onUnbind() {
mBinded = false; // when the activity closes we need to show the notification that user is connected to the sensor
// 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); createNotifcation(R.string.rsc_notification_connected_message, 0);
return super.onUnbind(intent);
} }
@Override @Override
@@ -184,7 +170,7 @@ public class RSCService extends BleProfileService implements RSCManagerCallbacks
/** /**
* Creates the notification * Creates the notification
* *
* @param messageResIdthe * @param messageResId
* message resource id. The message must have one String parameter,<br /> * message resource id. The message must have one String parameter,<br />
* f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code> * f.e. <code>&lt;string name="name"&gt;%s is connected&lt;/string&gt;</code>
* @param defaults * @param defaults
@@ -225,7 +211,7 @@ public class RSCService extends BleProfileService implements RSCManagerCallbacks
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(final Context context, final Intent intent) { 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()) if (isConnected())
getBinder().disconnect(); getBinder().disconnect();
else else

View File

@@ -22,10 +22,8 @@
package no.nordicsemi.android.nrftoolbox.rsc.settings; package no.nordicsemi.android.nrftoolbox.rsc.settings;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.R;

View File

@@ -21,9 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.scanner; package no.nordicsemi.android.nrftoolbox.scanner;
import java.util.ArrayList;
import no.nordicsemi.android.nrftoolbox.R;
import android.content.Context; import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -32,6 +29,10 @@ import android.widget.BaseAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; 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. * DeviceListAdapter class is list adapter for showing scanned Devices name, address and RSSI image based on RSSI values.
*/ */

View File

@@ -21,11 +21,6 @@
*/ */
package no.nordicsemi.android.nrftoolbox.scanner; 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.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
@@ -44,6 +39,12 @@ import android.widget.AdapterView;
import android.widget.Button; import android.widget.Button;
import android.widget.ListView; 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 * 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 * 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

View File

@@ -21,12 +21,12 @@
*/ */
package no.nordicsemi.android.nrftoolbox.scanner; package no.nordicsemi.android.nrftoolbox.scanner;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.util.Log; 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. * ScannerServiceParser is responsible to parse scanning data and it check if scanned device has required service in it.
*/ */

View File

@@ -22,11 +22,6 @@
package no.nordicsemi.android.nrftoolbox.uart; 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.ArgbEvaluator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@@ -40,6 +35,12 @@ import android.os.Bundle;
import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout;
import android.view.View; 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<UARTService.UARTBinder> implements UARTControlFragment.ControlFragmentListener, UARTInterface { public class UARTActivity extends BleProfileServiceReadyActivity<UARTService.UARTBinder> implements UARTControlFragment.ControlFragmentListener, UARTInterface {
private final static String SIS_EDIT_MODE = "sis_edit_mode"; private final static String SIS_EDIT_MODE = "sis_edit_mode";
@@ -236,5 +237,4 @@ public class UARTActivity extends BleProfileServiceReadyActivity<UARTService.UAR
} }
} }
} }
} }

View File

@@ -22,7 +22,6 @@
package no.nordicsemi.android.nrftoolbox.uart; package no.nordicsemi.android.nrftoolbox.uart;
import no.nordicsemi.android.nrftoolbox.R;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@@ -32,6 +31,8 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import no.nordicsemi.android.nrftoolbox.R;
public class UARTButtonAdapter extends BaseAdapter { public class UARTButtonAdapter extends BaseAdapter {
public final static String PREFS_BUTTON_ENABLED = "prefs_uart_enabled_"; public final static String PREFS_BUTTON_ENABLED = "prefs_uart_enabled_";
public final static String PREFS_BUTTON_COMMAND = "prefs_uart_command_"; public final static String PREFS_BUTTON_COMMAND = "prefs_uart_command_";

View File

@@ -22,7 +22,6 @@
package no.nordicsemi.android.nrftoolbox.uart; package no.nordicsemi.android.nrftoolbox.uart;
import no.nordicsemi.android.nrftoolbox.R;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@@ -38,6 +37,8 @@ import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.GridView; import android.widget.GridView;
import no.nordicsemi.android.nrftoolbox.R;
public class UARTControlFragment extends Fragment implements GridView.OnItemClickListener { public class UARTControlFragment extends Fragment implements GridView.OnItemClickListener {
private final static String TAG = "UARTControlFragment"; private final static String TAG = "UARTControlFragment";
private final static String SIS_EDIT_MODE = "sis_edit_mode"; private final static String SIS_EDIT_MODE = "sis_edit_mode";

View File

@@ -22,7 +22,6 @@
package no.nordicsemi.android.nrftoolbox.uart; package no.nordicsemi.android.nrftoolbox.uart;
import no.nordicsemi.android.nrftoolbox.R;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
@@ -42,6 +41,8 @@ import android.widget.EditText;
import android.widget.GridView; import android.widget.GridView;
import android.widget.ImageView; import android.widget.ImageView;
import no.nordicsemi.android.nrftoolbox.R;
public class UARTEditDialog extends DialogFragment implements View.OnClickListener, GridView.OnItemClickListener { public class UARTEditDialog extends DialogFragment implements View.OnClickListener, GridView.OnItemClickListener {
private final static String TAG = "UARTEditDialog"; private final static String TAG = "UARTEditDialog";
private final static String ARG_INDEX = "index"; private final static String ARG_INDEX = "index";

View File

@@ -22,9 +22,10 @@
package no.nordicsemi.android.nrftoolbox.uart; package no.nordicsemi.android.nrftoolbox.uart;
import no.nordicsemi.android.log.localprovider.LocalLogContentProvider;
import android.net.Uri; import android.net.Uri;
import no.nordicsemi.android.log.localprovider.LocalLogContentProvider;
public class UARTLocalLogContentProvider extends LocalLogContentProvider { public class UARTLocalLogContentProvider extends LocalLogContentProvider {
/** The authority for the contacts provider. */ /** The authority for the contacts provider. */
public final static String AUTHORITY = "no.nordicsemi.android.nrftoolbox.uart.log"; public final static String AUTHORITY = "no.nordicsemi.android.nrftoolbox.uart.log";

View File

@@ -22,10 +22,6 @@
package no.nordicsemi.android.nrftoolbox.uart; 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.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Color; import android.graphics.Color;
@@ -36,6 +32,11 @@ import android.view.ViewGroup;
import android.widget.CursorAdapter; import android.widget.CursorAdapter;
import android.widget.TextView; 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 { public class UARTLogAdapter extends CursorAdapter {
private static final SparseIntArray mColors = new SparseIntArray(); private static final SparseIntArray mColors = new SparseIntArray();

View File

@@ -22,10 +22,6 @@
package no.nordicsemi.android.nrftoolbox.uart; 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.ListFragment;
import android.app.LoaderManager.LoaderCallbacks; import android.app.LoaderManager.LoaderCallbacks;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
@@ -52,6 +48,11 @@ import android.widget.EditText;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; 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<Cursor> { public class UARTLogFragment extends ListFragment implements LoaderCallbacks<Cursor> {
private static final String SIS_LOG_SCROLL_POSITION = "sis_scroll_position"; private static final String SIS_LOG_SCROLL_POSITION = "sis_scroll_position";
private static final int LOG_SCROLL_NULL = -1; private static final int LOG_SCROLL_NULL = -1;

View File

@@ -22,166 +22,85 @@
package no.nordicsemi.android.nrftoolbox.uart; package no.nordicsemi.android.nrftoolbox.uart;
import java.util.List; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import no.nordicsemi.android.nrftoolbox.profile.BleManager; 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<UARTManagerCallbacks> {
private final static String TAG = "UARTManager";
public class UARTManager extends BleManager<UARTManagerCallbacks> {
/** Nordic UART Service UUID */
private final static UUID UART_SERVICE_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"); 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"); 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"); 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 BluetoothGattCharacteristic mTXCharacteristic, mRXCharacteristic;
private UARTManagerCallbacks mCallbacks;
private BluetoothGatt mBluetoothGatt;
private final Context mContext;
public UARTManager(final Context context) { public UARTManager(final Context context) {
mContext = context; super(context);
} }
@Override @Override
public void setGattCallbacks(final UARTManagerCallbacks callbacks) { protected BleManagerGattCallback getGattCallback() {
mCallbacks = callbacks; return mGattCallback;
}
@Override
public void connect(final Context context, final BluetoothDevice device) {
if (mBluetoothGatt == null) {
mBluetoothGatt = device.connectGatt(mContext, true, mGattCallback);
} else {
mBluetoothGatt.connect();
}
}
@Override
public void disconnect() {
if (mBluetoothGatt != null) {
mBluetoothGatt.disconnect();
}
}
public void send(final String text) {
if (mTXCharacteristic != null) {
mTXCharacteristic.setValue(text);
mBluetoothGatt.writeCharacteristic(mTXCharacteristic);
}
} }
/** /**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
*/ */
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() {
@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");
mCallbacks.onDeviceDisconnected(); @Override
closeBluetoothGatt(); protected Queue<Request> initGatt(final BluetoothGatt gatt) {
} final LinkedList<Request> requests = new LinkedList<>();
} else { requests.push(Request.newEnableNotificationsRequest(mRXCharacteristic));
mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status); return requests;
}
} }
@Override @Override
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) { public boolean isRequiredServiceSupported(final BluetoothGatt gatt) {
if (status == BluetoothGatt.GATT_SUCCESS) { final BluetoothGattService service = gatt.getService(UART_SERVICE_UUID);
final List<BluetoothGattService> services = gatt.getServices(); if (service != null) {
for (BluetoothGattService service : services) { mTXCharacteristic = service.getCharacteristic(UART_TX_CHARACTERISTIC_UUID);
if (service.getUuid().equals(UART_SERVICE_UUID)) { mRXCharacteristic = service.getCharacteristic(UART_RX_CHARACTERISTIC_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);
} }
return mTXCharacteristic != null && mRXCharacteristic != null;
} }
@Override @Override
public void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) { protected void onDeviceDisconnected() {
if (status == BluetoothGatt.GATT_SUCCESS) { mTXCharacteristic = null;
// do nothing mRXCharacteristic = null;
} else {
mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status);
}
} }
@Override @Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
if (status == BluetoothGatt.GATT_SUCCESS) { final String data = characteristic.getStringValue(0);
final String data = characteristic.getStringValue(0); mCallbacks.onDataSent(data);
mCallbacks.onDataSent(data);
} else {
mCallbacks.onError(ERROR_WRITE_DESCRIPTOR, status);
}
} }
@Override @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); final String data = characteristic.getStringValue(0);
mCallbacks.onDataReceived(data); 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) { public void send(final String text) {
gatt.setCharacteristicNotification(mRXCharacteristic, true); if (mTXCharacteristic != null) {
final BluetoothGattDescriptor descriptor = mRXCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID); mTXCharacteristic.setValue(text);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); writeCharacteristic(mTXCharacteristic);
gatt.writeDescriptor(descriptor);
}
@Override
public void closeBluetoothGatt() {
if (mBluetoothGatt != null) {
mBluetoothGatt.close();
mBluetoothGatt = null;
mTXCharacteristic = null;
mRXCharacteristic = null;
} }
mCallbacks = null;
} }
} }

View File

@@ -22,12 +22,6 @@
package no.nordicsemi.android.nrftoolbox.uart; 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.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@@ -35,9 +29,15 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager; 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 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_TX = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_UART_TX";
public static final String BROADCAST_UART_RX = "no.nordicsemi.android.nrftoolbox.uart.BROADCAST_UART_RX"; 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 final static int DISCONNECT_REQ = 97; // random
private UARTManager mManager; private UARTManager mManager;
private boolean mBinded;
private final LocalBinder mBinder = new UARTBinder(); private final LocalBinder mBinder = new UARTBinder();
@@ -95,24 +94,21 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
} }
@Override @Override
public IBinder onBind(final Intent intent) { protected void onRebind() {
mBinded = true;
return super.onBind(intent);
}
@Override
public void onRebind(final Intent intent) {
mBinded = true;
// when the activity rebinds to the service, remove the notification // when the activity rebinds to the service, remove the notification
cancelNotification(); cancelNotification();
} }
@Override @Override
public boolean onUnbind(final Intent intent) { protected void onUnbind() {
mBinded = false; // when the activity closes we need to show the notification that user is connected to the sensor
// 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);
createNotifcation(R.string.uart_notification_connected_message, 0); }
return super.onUnbind(intent);
@Override
protected void onServiceStarted() {
// logger is now available. Assign it to the manager
mManager.setLogger(getLogSession());
} }
@Override @Override
@@ -142,7 +138,7 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
* @param defaults * @param defaults
* signals that will be used to notify the user * 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); final Intent parentIntent = new Intent(this, FeaturesActivity.class);
parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); parentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent targetIntent = new Intent(this, UARTActivity.class); final Intent targetIntent = new Intent(this, UARTActivity.class);
@@ -177,7 +173,7 @@ public class UARTService extends BleProfileService implements UARTManagerCallbac
private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() { private final BroadcastReceiver mDisconnectActionBroadcastReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(final Context context, final Intent intent) { public void onReceive(final Context context, final Intent intent) {
Logger.i(getLogSession(), "Disconnect action pressed"); Logger.i(getLogSession(), "[Notification] Disconnect action pressed");
if (isConnected()) if (isConnected())
getBinder().disconnect(); getBinder().disconnect();
else else

View File

@@ -21,16 +21,17 @@
*/ */
package no.nordicsemi.android.nrftoolbox.utility; package no.nordicsemi.android.nrftoolbox.utility;
import no.nordicsemi.android.nrftoolbox.BuildConfig;
import android.util.Log; import android.util.Log;
import no.nordicsemi.android.nrftoolbox.BuildConfig;
public class DebugLogger { public class DebugLogger {
public static void v(final String tag, final String text) { public static void v(final String tag, final String text) {
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
Log.v(tag, text); 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) { if (BuildConfig.DEBUG) {
Log.d(tag, text); Log.d(tag, text);
} }
@@ -41,7 +42,7 @@ public class DebugLogger {
Log.i(tag, 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) { if (BuildConfig.DEBUG) {
Log.w(tag, text); Log.w(tag, text);
} }
@@ -52,9 +53,20 @@ public class DebugLogger {
Log.e(tag, 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) { if (BuildConfig.DEBUG) {
Log.wtf(tag, text); 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);
}
}
} }

View File

@@ -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);
}
}

View File

@@ -21,12 +21,13 @@
*/ */
package no.nordicsemi.android.nrftoolbox.widget; package no.nordicsemi.android.nrftoolbox.widget;
import no.nordicsemi.android.nrftoolbox.R;
import android.content.Context; import android.content.Context;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.TextView; import android.widget.TextView;
import no.nordicsemi.android.nrftoolbox.R;
public class TrebuchetBoldTextView extends TextView { public class TrebuchetBoldTextView extends TextView {
public TrebuchetBoldTextView(Context context) { public TrebuchetBoldTextView(Context context) {

View File

@@ -21,12 +21,13 @@
*/ */
package no.nordicsemi.android.nrftoolbox.widget; package no.nordicsemi.android.nrftoolbox.widget;
import no.nordicsemi.android.nrftoolbox.R;
import android.content.Context; import android.content.Context;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.TextView; import android.widget.TextView;
import no.nordicsemi.android.nrftoolbox.R;
public class TrebuchetTextView extends TextView { public class TrebuchetTextView extends TextView {
public TrebuchetTextView(Context context) { public TrebuchetTextView(Context context) {

View File

@@ -1,37 +0,0 @@
<!--
~ 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" >
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_menu_settings"
app:showAsAction="always"
android:title="@string/action_settings"/>
<item
android:id="@+id/action_about"
android:icon="@drawable/ic_menu_about"
app:showAsAction="always"
android:title="@string/action_about"/>
</menu>

View File

@@ -1,37 +0,0 @@
<!--
~ 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" >
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_menu_settings"
app:showAsAction="always"
android:title="@string/action_settings"/>
<item
android:id="@+id/action_about"
android:icon="@drawable/ic_menu_about"
app:showAsAction="always"
android:title="@string/action_about"/>
</menu>

Binary file not shown.

View File

@@ -55,6 +55,8 @@ INIT_FILE=""
INIT_PATH="" INIT_PATH=""
E_INIT_FILE="" E_INIT_FILE=""
TYPE=0 TYPE=0
# Modify this in order to keep the bond information after application upgrade
KEEP_BOND=false
# ================================================================================== # ==================================================================================
# Common methods # Common methods
@@ -186,11 +188,11 @@ fi
if [ "$ADDRESS" = "" ] ; then if [ "$ADDRESS" = "" ] ; then
# Start DFU Initiator activity if no target device specified # Start DFU Initiator activity if no target device specified
printf "Starting DFU Initiator activity..." 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 else
# Start the DFU service directly # Start the DFU service directly
printf "Starting DFU service..." 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 fi
if [ "$?" = "0" ] ; then if [ "$?" = "0" ] ; then

View File

@@ -53,6 +53,8 @@ set INIT_FILE=
set INIT_PATH= set INIT_PATH=
set E_INIT_FILE= set E_INIT_FILE=
set TYPE=0 set TYPE=0
rem Modify this in order to keep the bond information
set KEEP_BOND=false
rem ================================================================================== rem ==================================================================================
rem Check ADB rem Check ADB
@@ -195,6 +197,7 @@ if "%ADDRESS%"=="" (
echo|set /p=Starting DFU Initiator activity... echo|set /p=Starting DFU Initiator activity...
call adb %S_DEVICE% shell am start -a no.nordicsemi.android.action.DFU_UPLOAD^ call adb %S_DEVICE% shell am start -a no.nordicsemi.android.action.DFU_UPLOAD^
--ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE %TYPE%^ --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 -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/%HEX_FILE%" %E_INIT_FILE% | find "Error" > nul 2>&1
) else ( ) else (
rem Start DFU service on the device 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_ADDRESS %ADDRESS%^
-e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_NAME %NAME%^ -e no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_NAME %NAME%^
--ei no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE %TYPE%^ --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 -e no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH "/sdcard/Nordic Semiconductor/Upload/%HEX_FILE%" %E_INIT_FILE% | find "Error" > nul 2>&1
) )

View File

@@ -73,24 +73,28 @@
<string name="dfu_settings_dfu_number_of_packets_label">Number of packets</string> <string name="dfu_settings_dfu_number_of_packets_label">Number of packets</string>
<string name="dfu_settings_dfu_mbr_size">MBR size</string> <string name="dfu_settings_dfu_mbr_size">MBR size</string>
<string name="dfu_settings_dfu_mbr_size_title">MBR size (default 4096 = 0x1000)</string> <string name="dfu_settings_dfu_mbr_size_title">MBR size (default 4096 = 0x1000)</string>
<string name="dfu_settings_dfu_keep_bond">Keep bond information</string>
<string name="dfu_settings_dfu_about">About DFU</string> <string name="dfu_settings_dfu_about">About DFU</string>
<string name="dfu_settings_dfu_about_summary">DFU documentation on Nordic\'s Developer Zone</string> <string name="dfu_settings_dfu_about_summary">DFU documentation on Nordic\'s Developer Zone</string>
<string name="dfu_settings_dfu_information">Information</string> <string name="dfu_settings_dfu_information">Information</string>
<string name="dfu_settings_dfu_number_of_packets_info">During a DFU operation a lot of data packets are being sent to the target. The <i>onCharacteristicWrite(...)</i> callback <string name="dfu_settings_dfu_number_of_packets_info">During a DFU operation a lot of data packets are being sent to the target. The <i>onCharacteristicWrite(...)</i>
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 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
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.</string> 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.</string>
<string name="dfu_file_type_title">Select file type</string> <string name="dfu_file_type_title">Select file type</string>
<string-array name="dfu_file_type"> <string-array name="dfu_file_type">
<item>Soft Device</item> <item>Distribution packet (ZIP)</item>
<item>Bootloader</item> <item>Soft Device</item>
<item>Application</item> <item>Bootloader</item>
<item>Multiple files (ZIP)</item> <item>Application</item>
</string-array> </string-array>
<string name="dfu_file_init_title">Init packet</string> <string name="dfu_file_init_title">Init packet</string>
<string name="dfu_file_init_message">Do you want to select the Init packet file?\n <string name="dfu_file_init_message">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 The Init packet file (*.dat) should contain the device type and revision, application version, list of supported Soft Devices and the firmware CRC
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 <b>required</b>.</string> 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 <b>required</b>.
</string>
<string name="dfu_unknown_name">unnamed device</string> <string name="dfu_unknown_name">unnamed device</string>
<string name="dfu_uploading_percentage_label">0%</string> <string name="dfu_uploading_percentage_label">0%</string>
@@ -104,16 +108,19 @@
<string name="dfu_help_title">Select file</string> <string name="dfu_help_title">Select file</string>
<string name="dfu_help_message">A file browser application must be installed on the device before selecting the file. <string name="dfu_help_message">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. 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\nYou will be asked to select an application if more than one is installed. A single application will be launched automatically.
\n\nSince <b>Android KitKat</b> you may use the preinstalled document picker application. Ensure <i>Display advanced devices</i> option is enabled in settings to use the <i>Internal storage</i>. \n\nSince <b>Android KitKat</b> you may use the preinstalled document picker application. Ensure <i>Display advanced devices</i> option is enabled in settings to use the <i>Internal storage</i>.
\n\nHeart Rate Monitor and Running Speed and Cadence applications were copied to <i>Nordic Semiconductor</i> folder in the internal storage.</string> \n\nSample applications were copied to <i>Nordic Semiconductor</i> folder in the internal storage.</string>
<string name="dfu_zip_info_text">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. <string name="dfu_zip_info_text">Starting from nRF Toolbox v1.12 the new Distribution packet (ZIP) is the recommended method for distributing firmware upgrades.
\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. You can create the ZIP file using the <b>nrf utility</b> tool, which is part of Master Control Panel 3.8.0+. For more detailed information, see the DFU documentation.
\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.</string> \n\n<b>Backward compatibility</b>
<string name="dfu_about_text">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 \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:
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. </string>
\n\nFor more information about the DFU see the About DFU section in Settings.</string> <string name="dfu_about_text">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.</string>
</resources> </resources>

View File

@@ -27,8 +27,8 @@
<dimen name="proximity_feature_title_long_margin">-142dp</dimen> <dimen name="proximity_feature_title_long_margin">-142dp</dimen>
<string name="proximity_default_name">DEFAULT PROXIMITY</string> <string name="proximity_default_name">DEFAULT PROXIMITY</string>
<string name="proximity_action_findme">FindMe</string> <string name="proximity_action_findme">Find Me</string>
<string name="proximity_action_silentme">SilentMe</string> <string name="proximity_action_silentme">Silent Me</string>
<string name="proximity_lock_image_description">LOCK Image</string> <string name="proximity_lock_image_description">LOCK Image</string>
<string name="proximity_notification_action_disconnect">Disconnect</string> <string name="proximity_notification_action_disconnect">Disconnect</string>
<string name="proximity_notification_linkloss_alert">%s is getting away!</string> <string name="proximity_notification_linkloss_alert">%s is getting away!</string>

View File

@@ -41,6 +41,10 @@
android:key="settings_mbr_size" android:key="settings_mbr_size"
android:dialogTitle="@string/dfu_settings_dfu_mbr_size_title" android:dialogTitle="@string/dfu_settings_dfu_mbr_size_title"
android:title="@string/dfu_settings_dfu_mbr_size" /> android:title="@string/dfu_settings_dfu_mbr_size" />
<SwitchPreference
android:defaultValue="false"
android:key="settings_keep_bond"
android:title="@string/dfu_settings_dfu_keep_bond" />
<no.nordicsemi.android.nrftoolbox.dfu.settings.AboutDfuPreference <no.nordicsemi.android.nrftoolbox.dfu.settings.AboutDfuPreference
android:summary="@string/dfu_settings_dfu_about_summary" android:summary="@string/dfu_settings_dfu_about_summary"

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4"> <module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager"> <component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle"> <facet type="java-gradle" name="Java-Gradle">
<configuration> <configuration>
@@ -7,9 +7,7 @@
</configuration> </configuration>
</facet> </facet>
</component> </component>
<component name="NewModuleRootManager" inherit-compiler-output="false"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<output url="file://$MODULE_DIR$/build/classes/main" />
<output-test url="file://$MODULE_DIR$/build/classes/test" />
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" /> <excludeFolder url="file://$MODULE_DIR$/.gradle" />