Template profile rewritten to use new BleManager API

This commit is contained in:
Aleksander Nowakowski
2018-05-24 15:11:35 +02:00
parent 025f4118db
commit b23eee2763
8 changed files with 287 additions and 108 deletions

View File

@@ -27,8 +27,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;
import android.view.Menu;
import android.view.View;
import android.widget.TextView;
import java.util.UUID;
@@ -47,7 +49,7 @@ public class TemplateActivity extends BleProfileServiceReadyActivity<TemplateSer
// TODO change view references to match your need
private TextView mValueView;
private TextView mValueUnitView;
private TextView mBatteryLevelView;
@Override
protected void onCreateView(final Bundle savedInstanceState) {
@@ -59,7 +61,9 @@ public class TemplateActivity extends BleProfileServiceReadyActivity<TemplateSer
private void setGUI() {
// TODO assign your views to fields
mValueView = findViewById(R.id.value);
mValueUnitView = findViewById(R.id.value_unit);
mBatteryLevelView = findViewById(R.id.battery);
findViewById(R.id.action_set_name).setOnClickListener(v -> getService().performAction("Template"));
}
@Override
@@ -77,16 +81,7 @@ public class TemplateActivity extends BleProfileServiceReadyActivity<TemplateSer
protected void setDefaultUI() {
// TODO clear your UI
mValueView.setText(R.string.not_available_value);
}
@Override
protected void onServiceBound(final TemplateService.TemplateBinder binder) {
// not used
}
@Override
protected void onServiceUnbound() {
// not used
mBatteryLevelView.setText(R.string.not_available);
}
@Override
@@ -133,25 +128,53 @@ public class TemplateActivity extends BleProfileServiceReadyActivity<TemplateSer
return TemplateService.class;
}
@Override
protected void onServiceBound(final TemplateService.TemplateBinder binder) {
// not used
}
@Override
protected void onServiceUnbound() {
// not used
}
@Override
public void onServicesDiscovered(final BluetoothDevice device, final boolean optionalServicesFound) {
// this may notify user or show some views
}
private void setValueOnView(final int value) {
@Override
public void onDeviceDisconnected(final BluetoothDevice device) {
super.onDeviceDisconnected(device);
mBatteryLevelView.setText(R.string.not_available);
}
// Handling updates from the device
@SuppressWarnings("unused")
private void setValueOnView(@NonNull final BluetoothDevice device, final int value) {
// TODO assign the value to a view
mValueView.setText(String.valueOf(value));
}
@SuppressWarnings("unused")
public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int value) {
mBatteryLevelView.setText(getString(R.string.battery, value));
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
final BluetoothDevice device = intent.getParcelableExtra(TemplateService.EXTRA_DEVICE);
if (TemplateService.BROADCAST_TEMPLATE_MEASUREMENT.equals(action)) {
final int value = intent.getIntExtra(TemplateService.EXTRA_DATA, 0);
// Update GUI
setValueOnView(value);
setValueOnView(device, value);
} else if (TemplateService.BROADCAST_BATTERY_LEVEL.equals(action)) {
final int batteryLevel = intent.getIntExtra(TemplateService.EXTRA_BATTERY_LEVEL, 0);
// Update GUI
onBatteryLevelChanged(device, batteryLevel);
}
}
};
@@ -159,6 +182,7 @@ public class TemplateActivity extends BleProfileServiceReadyActivity<TemplateSer
private static IntentFilter makeIntentFilter() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TemplateService.BROADCAST_TEMPLATE_MEASUREMENT);
intentFilter.addAction(TemplateService.BROADCAST_BATTERY_LEVEL);
return intentFilter;
}
}

View File

@@ -21,34 +21,43 @@
*/
package no.nordicsemi.android.nrftoolbox.template;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import android.support.annotation.NonNull;
import java.util.Deque;
import java.util.LinkedList;
import java.util.UUID;
import no.nordicsemi.android.ble.BleManager;
import no.nordicsemi.android.ble.Request;
import no.nordicsemi.android.ble.data.Data;
import no.nordicsemi.android.log.LogContract;
import no.nordicsemi.android.nrftoolbox.parser.TemplateParser;
import no.nordicsemi.android.nrftoolbox.battery.BatteryManager;
import no.nordicsemi.android.nrftoolbox.template.callback.TemplateDataCallback;
/**
* Modify to template manager to match your requirements.
* The TemplateManager extends {@link BatteryManager}, but it may easily extend {@link BleManager}
* instead if you don't need Battery Service support. If not, also modify the
* {@link TemplateManagerCallbacks} to extend {@link no.nordicsemi.android.ble.BleManagerCallbacks}
* and replace BatteryManagerGattCallback to BleManagerGattCallback in this class.
*/
public class TemplateManager extends BleManager<TemplateManagerCallbacks> {
private static final String TAG = "TemplateManager";
public class TemplateManager extends BatteryManager<TemplateManagerCallbacks> {
// TODO Replace the services and characteristics below to match your device.
/** The service UUID */
public final static UUID SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb"); // TODO change the UUID to your match your service
/** The characteristic UUID */
private static final UUID MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb"); // TODO change the UUID to your match your characteristic
static final UUID SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb"); // Heart Rate service
/** A UUID of a characteristic with notify property */
private static final UUID MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb"); // Heart Rate Measurement
/** A UUID of a characteristic with read property */
private static final UUID READABLE_CHARACTERISTIC_UUID = UUID.fromString("00002A38-0000-1000-8000-00805f9b34fb"); // Body Sensor Location
/** Some other service UUID */
private static final UUID OTHER_SERVICE_UUID = UUID.fromString("00001800-0000-1000-8000-00805f9b34fb"); // Generic Access service
/** A UUID of a characteristic with write property */
private static final UUID WRITABLE_CHARACTERISTIC_UUID = UUID.fromString("00002A00-0000-1000-8000-00805f9b34fb"); // Device Name
// TODO add more services and characteristics, if required
private BluetoothGattCharacteristic mCharacteristic;
// TODO Add more services and characteristics references.
private BluetoothGattCharacteristic mRequiredCharacteristic, mDeviceNameCharacteristic, mOptionalCharacteristic;
public TemplateManager(final Context context) {
super(context);
@@ -56,83 +65,147 @@ public class TemplateManager extends BleManager<TemplateManagerCallbacks> {
@NonNull
@Override
protected BleManagerGattCallback getGattCallback() {
protected BatteryManagerGattCallback getGattCallback() {
return mGattCallback;
}
/**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
*/
private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() {
private final BatteryManagerGattCallback mGattCallback = new BatteryManagerGattCallback() {
@Override
protected Deque<Request> initGatt(@NonNull final BluetoothGatt gatt) {
final LinkedList<Request> requests = new LinkedList<>();
// TODO initialize your device, enable required notifications and indications, write what needs to be written to start working
requests.add(Request.newEnableNotificationsRequest(mCharacteristic));
return requests;
protected void initialize() {
// Initialize the Battery Manager. It will enable Battery Level notifications.
// Remove it if you don't need this feature.
super.initialize();
// TODO Initialize your manager here.
// Initialization is done once, after the device is connected. Usually it should
// enable notifications or indications on some characteristics, write some data or
// read some features / version.
// After the initialization is complete, the onDeviceReady(...) method will be called.
// Increase the MTU
requestMtu(43)
.with((device, mtu) -> log(LogContract.Log.Level.APPLICATION, "MTU changed to " + mtu))
.done(device -> {
// You may do some logic in here that should be done when the request finished successfully.
// In case of MTU this method is called also when the MTU hasn't changed, or has changed
// to a different (lower) value. Use .with(...) to get the MTU value.
})
.fail((device, status) -> log(LogContract.Log.Level.WARNING, "MTU change not supported"));
// Set notification callback
setNotificationCallback(mRequiredCharacteristic)
// This callback will be called each time the notification is received
.with(new TemplateDataCallback() {
@Override
public void onSampleValueReceived(@NonNull final BluetoothDevice device, final int value) {
// Let's lass received data to the service
mCallbacks.onSampleValueReceived(device, value);
}
@Override
public void onInvalidDataReceived(@NonNull final BluetoothDevice device, @NonNull final Data data) {
log(LogContract.Log.Level.WARNING, "Invalid data received: " + data);
}
});
// Enable notifications
enableNotifications(mRequiredCharacteristic)
// Method called after the data were sent (data will contain 0x0100 in this case)
.with((device, data) -> log(LogContract.Log.Level.DEBUG, "Data sent: " + data))
// Method called when the request finished successfully. This will be called after .with(..) callback
.done(device -> log(LogContract.Log.Level.APPLICATION, "Notifications enabled successfully"))
// Methods called in case of an error, for example when the characteristic does not have Notify property
.fail((device, status) -> log(LogContract.Log.Level.WARNING, "Failed to enable notifications"));
}
@Override
protected boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) {
// TODO Initialize required characteristics.
// It should return true if all has been discovered (that is that device is supported).
final BluetoothGattService service = gatt.getService(SERVICE_UUID);
if (service != null) {
mCharacteristic = service.getCharacteristic(MEASUREMENT_CHARACTERISTIC_UUID);
mRequiredCharacteristic = service.getCharacteristic(MEASUREMENT_CHARACTERISTIC_UUID);
}
return mCharacteristic != null;
final BluetoothGattService otherService = gatt.getService(OTHER_SERVICE_UUID);
if (otherService != null) {
mDeviceNameCharacteristic = otherService.getCharacteristic(WRITABLE_CHARACTERISTIC_UUID);
}
return mRequiredCharacteristic != null && mDeviceNameCharacteristic != null;
}
@Override
protected boolean isOptionalServiceSupported(@NonNull final BluetoothGatt gatt) {
// Initialize Battery characteristic
super.isOptionalServiceSupported(gatt);
// TODO If there are some optional characteristics, initialize them there.
final BluetoothGattService service = gatt.getService(SERVICE_UUID);
if (service != null) {
mOptionalCharacteristic = service.getCharacteristic(READABLE_CHARACTERISTIC_UUID);
}
return mOptionalCharacteristic != null;
}
@Override
protected void onDeviceDisconnected() {
mCharacteristic = null;
}
// Release Battery Service
super.onDeviceDisconnected();
// TODO implement data handlers. Methods below are called after the initialization is complete.
// TODO Release references to your characteristics.
mRequiredCharacteristic = null;
mDeviceNameCharacteristic = null;
mOptionalCharacteristic = null;
}
@Override
protected void onDeviceReady() {
super.onDeviceReady();
// TODO initialization is now ready. The activity is being notified using TemplateManagerCallbacks#onDeviceReady() method.
// This method may be removed from this class if not required as the super class implementation handles this event.
}
// Initialization is now ready.
// The service or activity has been notified with TemplateManagerCallbacks#onDeviceReady().
// TODO Do some extra logic here, of remove onDeviceReady().
@Override
protected void onCharacteristicNotified(@NonNull final BluetoothGatt gatt, @NonNull final BluetoothGattCharacteristic characteristic) {
// TODO this method is called when a notification has been received
// This method may be removed from this class if not required
log(LogContract.Log.Level.APPLICATION, "\"" + TemplateParser.parse(characteristic) + "\" received");
int value;
final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
if ((flags & 0x01) > 0) {
value = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1);
// Device is ready, let's read something here. Usually there is nothing else to be done
// here, as all had been done during initialization.
readCharacteristic(mOptionalCharacteristic)
.with((device, data) -> {
// Characteristic value has been read
// Let's do some magic with it.
if (data.size() > 0) {
final Integer value = data.getIntValue(Data.FORMAT_UINT8, 0);
log(LogContract.Log.Level.APPLICATION, "Value '" + value + "' has been read!");
} else {
value = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1);
log(LogContract.Log.Level.WARNING, "Value is empty!");
}
//This will send callback to the Activity when new value is received from HR device
mCallbacks.onSampleValueReceived(gatt.getDevice(), value);
}
@Override
protected void onCharacteristicIndicated(@NonNull final BluetoothGatt gatt, @NonNull final BluetoothGattCharacteristic characteristic) {
// TODO this method is called when an indication has been received
// This method may be removed from this class if not required
}
@Override
protected void onCharacteristicRead(@NonNull final BluetoothGatt gatt, @NonNull final BluetoothGattCharacteristic characteristic) {
// TODO this method is called when the characteristic has been read
// This method may be removed from this class if not required
}
@Override
protected void onCharacteristicWrite(@NonNull final BluetoothGatt gatt, @NonNull final BluetoothGattCharacteristic characteristic) {
// TODO this method is called when the characteristic has been written
// This method may be removed from this class if not required
});
}
};
// TODO Define manager's API
/**
* This method will write important data to the device.
*
* @param parameter parameter to be written.
*/
void performAction(final String parameter) {
log(LogContract.Log.Level.VERBOSE, "Changing device name to \"" + parameter + "\"");
// Write some data to the characteristic.
writeCharacteristic(mDeviceNameCharacteristic, Data.from(parameter))
// If data are longer than MTU-3, they will be chunked into multiple packets.
// Check out other split options, with .split(...).
.split()
// Callback called when data were sent, or added to outgoing queue is Write Without Request
// type has been chosen.
.with((device, data) -> log(LogContract.Log.Level.DEBUG, data.size() + " bytes were sent"))
// Callback called when data were sent, or added to outgoing queue is Write Without Request
// type has been chosen.
.done(device -> log(LogContract.Log.Level.APPLICATION, "Device name set to \"" + parameter + "\""))
// Callback called when write has failed.
.fail((device, status) -> log(LogContract.Log.Level.WARNING, "Failed to change device name"));
}
}

View File

@@ -21,24 +21,18 @@
*/
package no.nordicsemi.android.nrftoolbox.template;
import android.bluetooth.BluetoothDevice;
import no.nordicsemi.android.ble.BleManagerCallbacks;
import no.nordicsemi.android.nrftoolbox.battery.BatteryManagerCallbacks;
import no.nordicsemi.android.nrftoolbox.template.callback.TemplateCharacteristicCallback;
/**
* Interface {@link TemplateManagerCallbacks} must be implemented by {@link TemplateActivity} in order to receive callbacks from {@link TemplateManager}
* Interface {@link TemplateManagerCallbacks} must be implemented by {@link TemplateService}
* in order to receive callbacks from {@link TemplateManager}
*/
public interface TemplateManagerCallbacks extends BleManagerCallbacks {
interface TemplateManagerCallbacks extends BatteryManagerCallbacks, TemplateCharacteristicCallback {
// TODO add more callbacks. Callbacks are called when a data has been received/written to a remote device. This is the way how the manager notifies the activity about this event.
/**
* Called when a value is received.
*
* @param device a device from which the value was obtained
* @param value
* the new value
*/
void onSampleValueReceived(final BluetoothDevice device, int value);
// Callbacks are called when a data has been received/written to a remote device.
// This is the way how the manager notifies the activity about this event.
// TODO add more callbacks.
// If you need, create more ...Callback interfaces and extend this interface with them.
}

View File

@@ -30,6 +30,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
@@ -44,6 +45,9 @@ public class TemplateService extends BleProfileService implements TemplateManage
public static final String BROADCAST_TEMPLATE_MEASUREMENT = "no.nordicsemi.android.nrftoolbox.template.BROADCAST_MEASUREMENT";
public static final String EXTRA_DATA = "no.nordicsemi.android.nrftoolbox.template.EXTRA_DATA";
public static final String BROADCAST_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.BROADCAST_BATTERY_LEVEL";
public static final String EXTRA_BATTERY_LEVEL = "no.nordicsemi.android.nrftoolbox.EXTRA_BATTERY_LEVEL";
private final static String ACTION_DISCONNECT = "no.nordicsemi.android.nrftoolbox.template.ACTION_DISCONNECT";
private final static int NOTIFICATION_ID = 864;
@@ -55,16 +59,19 @@ public class TemplateService extends BleProfileService implements TemplateManage
private final LocalBinder mBinder = new TemplateBinder();
/**
* This local binder is an interface for the bonded activity to operate with the HTS sensor
* This local binder is an interface for the bound activity to operate with the sensor.
*/
public class TemplateBinder extends LocalBinder {
// empty
// TODO add some service API that mey be used from the Activity
class TemplateBinder extends LocalBinder {
// TODO Define service API that may be used by a bound Activity
// public void setLights(final boolean on) {
// Logger.v(getLogSession(), "Light set to: " + on);
// mManager.setLights(on);
// }
/**
* Sends some important data to the device.
*
* @param parameter some parameter.
*/
public void performAction(final String parameter) {
mManager.performAction(parameter);
}
}
@Override
@@ -108,7 +115,7 @@ public class TemplateService extends BleProfileService implements TemplateManage
}
@Override
public void onSampleValueReceived(final BluetoothDevice device, final int value) {
public void onSampleValueReceived(@NonNull final BluetoothDevice device, final int value) {
final Intent broadcast = new Intent(BROADCAST_TEMPLATE_MEASUREMENT);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
broadcast.putExtra(EXTRA_DATA, value);
@@ -120,14 +127,17 @@ public class TemplateService extends BleProfileService implements TemplateManage
}
}
@Override
public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) {
}
/**
* Creates the notification
* Creates the notification.
*
* @param messageResId
* message resource id. The message must have one String parameter,<br />
* @param messageResId 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>
* @param defaults
* signals that will be used to notify the user
* @param defaults signals that will be used to notify the user
*/
private void createNotification(final int messageResId, final int defaults) {
final Intent parentIntent = new Intent(this, FeaturesActivity.class);
@@ -138,7 +148,7 @@ public class TemplateService extends BleProfileService implements TemplateManage
final PendingIntent disconnectAction = PendingIntent.getBroadcast(this, DISCONNECT_REQ, disconnect, PendingIntent.FLAG_UPDATE_CURRENT);
// both activities above have launchMode="singleTask" in the AndroidManifest.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 NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL);
builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
@@ -172,5 +182,4 @@ public class TemplateService extends BleProfileService implements TemplateManage
stopSelf();
}
};
}

View File

@@ -0,0 +1,21 @@
package no.nordicsemi.android.nrftoolbox.template.callback;
import android.bluetooth.BluetoothDevice;
import android.support.annotation.NonNull;
/**
* This class defines your characteristic API.
* In this example (that is the HRM characteristic, which the template is based on), is notifying
* with a value (Heart Rate). The single method just returns the value and ignores other
* optional data from Heart Rate Measurement characteristic for simplicity.
*/
public interface TemplateCharacteristicCallback {
/**
* Called when a value is received.
*
* @param device a device from which the value was obtained.
* @param value the new value.
*/
void onSampleValueReceived(@NonNull final BluetoothDevice device, int value);
}

View File

@@ -0,0 +1,47 @@
package no.nordicsemi.android.nrftoolbox.template.callback;
import android.bluetooth.BluetoothDevice;
import android.support.annotation.NonNull;
import no.nordicsemi.android.ble.callback.profile.ProfileDataCallback;
import no.nordicsemi.android.ble.data.Data;
/**
* This is a sample data callback, that's based on Heart Rate Measurement characteristic.
* It parses the HR value and ignores other optional data for simplicity.
* Check {@link no.nordicsemi.android.ble.common.callback.hr.HeartRateMeasurementDataCallback}
* for full implementation.
*
* TODO Modify the content to parse your data.
*/
@SuppressWarnings("ConstantConditions")
public abstract class TemplateDataCallback implements ProfileDataCallback, TemplateCharacteristicCallback {
@Override
public void onDataReceived(@NonNull final BluetoothDevice device, @NonNull final Data data) {
if (data.size() < 2) {
onInvalidDataReceived(device, data);
return;
}
// Read flags
int offset = 0;
final int flags = data.getIntValue(Data.FORMAT_UINT8, offset);
final int hearRateType = (flags & 0x01) == 0 ? Data.FORMAT_UINT8 : Data.FORMAT_UINT16;
offset += 1;
// Validate packet length. The type's lower nibble is its length.
if (data.size() < 1 + (hearRateType & 0x0F)) {
onInvalidDataReceived(device, data);
return;
}
final int value = data.getIntValue(hearRateType, offset);
// offset += hearRateType & 0xF;
// ...
// Report the parsed value(s)
onSampleValueReceived(device, value);
}
}

View File

@@ -79,6 +79,7 @@
<!-- Application section -->
<LinearLayout
android:id="@+id/container"
style="@style/Widget.List"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -131,6 +132,15 @@
</TableLayout>
</LinearLayout>
<Button
android:id="@+id/action_set_name"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/container"
android:text="@string/template_action"/>
<Button
android:id="@+id/action_connect"
style="@style/Widget.Connect"

View File

@@ -31,6 +31,7 @@
<string name="template_section_header">Values</string>
<string name="template_value_title">Value</string>
<string name="template_unit_bpm">bpm</string>
<string name="template_action">An action</string>
<string name="template_notification_action_disconnect">Disconnect</string>
<string name="template_notification_connected_message">%s is connected.</string>