CGMS refactored to use new BLE Common Library

This commit is contained in:
Aleksander Nowakowski
2018-04-24 23:19:32 +02:00
parent b7aa00fb4f
commit b8ec4f3499
4 changed files with 409 additions and 464 deletions

View File

@@ -28,20 +28,22 @@ import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothGattService;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.SparseArray; import android.util.SparseArray;
import java.util.Deque;
import java.util.LinkedList;
import java.util.UUID; import java.util.UUID;
import no.nordicsemi.android.ble.BleManager; import no.nordicsemi.android.ble.common.callback.RecordAccessControlPointDataCallback;
import no.nordicsemi.android.ble.Request; import no.nordicsemi.android.ble.common.callback.cgm.CGMSpecificOpsControlPointDataCallback;
import no.nordicsemi.android.ble.common.callback.cgm.ContinuousGlucoseMeasurementDataCallback;
import no.nordicsemi.android.ble.common.data.RecordAccessControlPointData;
import no.nordicsemi.android.ble.common.data.cgm.CGMSpecificOpsControlPointData;
import no.nordicsemi.android.ble.data.Data;
import no.nordicsemi.android.log.LogContract; import no.nordicsemi.android.log.LogContract;
import no.nordicsemi.android.nrftoolbox.battery.BatteryManager; import no.nordicsemi.android.nrftoolbox.battery.BatteryManager;
import no.nordicsemi.android.nrftoolbox.parser.CGMMeasurementParser; import no.nordicsemi.android.nrftoolbox.parser.CGMMeasurementParser;
import no.nordicsemi.android.nrftoolbox.parser.CGMSpecificOpsControlPointParser; import no.nordicsemi.android.nrftoolbox.parser.CGMSpecificOpsControlPointParser;
import no.nordicsemi.android.nrftoolbox.parser.RecordAccessControlPointParser; import no.nordicsemi.android.nrftoolbox.parser.RecordAccessControlPointParser;
import no.nordicsemi.android.nrftoolbox.utility.DebugLogger;
public class CGMSManager extends BatteryManager<CGMSManagerCallbacks> { public class CGMSManager extends BatteryManager<CGMSManagerCallbacks> {
private static final String TAG = "CGMSManager"; private static final String TAG = "CGMSManager";
@@ -50,6 +52,7 @@ public class CGMSManager extends BatteryManager<CGMSManagerCallbacks> {
* Cycling Speed and Cadence service UUID * Cycling Speed and Cadence service UUID
*/ */
public static final UUID CGMS_UUID = UUID.fromString("0000181F-0000-1000-8000-00805f9b34fb"); public static final UUID CGMS_UUID = UUID.fromString("0000181F-0000-1000-8000-00805f9b34fb");
private static final UUID CGM_FEAURE_UUID = UUID.fromString("00002AA8-0000-1000-8000-00805f9b34fb");
private static final UUID CGM_MEASUREMENT_UUID = UUID.fromString("00002AA7-0000-1000-8000-00805f9b34fb"); private static final UUID CGM_MEASUREMENT_UUID = UUID.fromString("00002AA7-0000-1000-8000-00805f9b34fb");
private static final UUID CGM_OPS_CONTROL_POINT_UUID = UUID.fromString("00002AAC-0000-1000-8000-00805f9b34fb"); private static final UUID CGM_OPS_CONTROL_POINT_UUID = UUID.fromString("00002AAC-0000-1000-8000-00805f9b34fb");
/** /**
@@ -57,49 +60,15 @@ public class CGMSManager extends BatteryManager<CGMSManagerCallbacks> {
*/ */
private static final UUID RACP_UUID = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb"); private static final UUID RACP_UUID = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb");
private static final int OP_CODE_REPORT_STORED_RECORDS = 1; private BluetoothGattCharacteristic mCGMFeatureCharacteristic;
private static final int OP_CODE_DELETE_STORED_RECORDS = 2;
private static final int OP_CODE_ABORT_OPERATION = 3;
private static final int OP_CODE_REPORT_NUMBER_OF_RECORDS = 4;
private static final int OP_CODE_NUMBER_OF_STORED_RECORDS_RESPONSE = 5;
private static final int OP_CODE_RESPONSE_CODE = 6;
private static final int OPERATOR_NULL = 0;
private static final int OPERATOR_ALL_RECORDS = 1;
private static final int OPERATOR_LESS_THEN_OR_EQUAL = 2;
private static final int OPERATOR_GREATER_THEN_OR_EQUAL = 3;
private static final int OPERATOR_WITHING_RANGE = 4;
private static final int OPERATOR_FIRST_RECORD = 5;
private static final int OPERATOR_LAST_RECORD = 6;
/**
* The filter type is used for range operators ({@link #OPERATOR_LESS_THEN_OR_EQUAL}, {@link #OPERATOR_GREATER_THEN_OR_EQUAL}, {@link #OPERATOR_WITHING_RANGE}.<br/>
* The syntax of the operand is: [Filter Type][Minimum][Maximum].<br/>
* This filter selects the records by the sequence number.
*/
private static final int FILTER_TYPE_SEQUENCE_NUMBER = 1;
/**
* The filter type is used for range operators ({@link #OPERATOR_LESS_THEN_OR_EQUAL}, {@link #OPERATOR_GREATER_THEN_OR_EQUAL}, {@link #OPERATOR_WITHING_RANGE}.<br/>
* The syntax of the operand is: [Filter Type][Minimum][Maximum].<br/>
* This filter selects the records by the user facing time (base time + offset time).
*/
private static final int FILTER_TYPE_USER_FACING_TIME = 2;
private static final int RESPONSE_SUCCESS = 1;
private static final int RESPONSE_OP_CODE_NOT_SUPPORTED = 2;
private static final int RESPONSE_INVALID_OPERATOR = 3;
private static final int RESPONSE_OPERATOR_NOT_SUPPORTED = 4;
private static final int RESPONSE_INVALID_OPERAND = 5;
private static final int RESPONSE_NO_RECORDS_FOUND = 6;
private static final int RESPONSE_ABORT_UNSUCCESSFUL = 7;
private static final int RESPONSE_PROCEDURE_NOT_COMPLETED = 8;
private static final int RESPONSE_OPERAND_NOT_SUPPORTED = 9;
private BluetoothGattCharacteristic mCGMMeasurementCharacteristic; private BluetoothGattCharacteristic mCGMMeasurementCharacteristic;
private BluetoothGattCharacteristic mCGMOpsControlPointCharacteristic; private BluetoothGattCharacteristic mCGMSpecificOpsControlPointCharacteristic;
private BluetoothGattCharacteristic mRecordAccessControlPointCharacteristic; private BluetoothGattCharacteristic mRecordAccessControlPointCharacteristic;
private SparseArray<CGMSRecord> mRecords = new SparseArray<>(); private SparseArray<CGMSRecord> mRecords = new SparseArray<>();
private boolean mAbort; private boolean mAbort;
/** A flag set to true if the remote device supports E2E CRC. */
private boolean mSecured;
private long mSessionStartTime; private long mSessionStartTime;
CGMSManager(final Context context) { CGMSManager(final Context context) {
@@ -118,164 +87,151 @@ public class CGMSManager extends BatteryManager<CGMSManagerCallbacks> {
@Override @Override
protected void initialize(@NonNull final BluetoothDevice device) { protected void initialize(@NonNull final BluetoothDevice device) {
// Enable Battery service
super.initialize(device); super.initialize(device);
enableNotifications(mCGMMeasurementCharacteristic);
enableIndications(mCGMOpsControlPointCharacteristic) // Enable Continuous Glucose Measurement notifications
.done(() -> mSessionStartTime = System.currentTimeMillis()); enableNotifications(mCGMMeasurementCharacteristic)
writeCharacteristic(mCGMOpsControlPointCharacteristic, new byte[] { 26 }); .with(new ContinuousGlucoseMeasurementDataCallback() {
enableIndications(mRecordAccessControlPointCharacteristic); @Override
public void onDataReceived(@NonNull final BluetoothDevice device, @NonNull final Data data) {
log(LogContract.Log.Level.APPLICATION, "\"" + CGMMeasurementParser.parse(mCGMMeasurementCharacteristic) + "\" received");
super.onDataReceived(device, data);
}
@Override
public void onContinuousGlucoseMeasurementReceived(@NonNull final BluetoothDevice device, final float glucoseConcentration, @Nullable final Float cgmTrend, @Nullable final Float cgmQuality, final CGMStatus status, final int timeOffset, final boolean secured) {
final long timestamp = mSessionStartTime + (timeOffset * 60000L); // Sequence number is in minutes since Start Session
final CGMSRecord record = new CGMSRecord(timeOffset, glucoseConcentration, timestamp);
mCallbacks.onCGMValueReceived(device, record);
}
@Override
public void onContinuousGlucoseMeasurementReceivedWithCrcError(@NonNull final BluetoothDevice device, @NonNull final Data data) {
log(LogContract.Log.Level.WARNING, "Continuous Glucose Measurement record received with CRC error");
}
})
.fail((error) -> log(LogContract.Log.Level.WARNING, "Failed to enable Continuous Glucose Measurement notifications (" + error + ")"));
// Enable CGM Specific Ops indications
enableIndications(mCGMSpecificOpsControlPointCharacteristic)
.with(new CGMSpecificOpsControlPointDataCallback() {
@Override
public void onDataReceived(@NonNull final BluetoothDevice device, @NonNull final Data data) {
log(LogContract.Log.Level.APPLICATION, "\"" + CGMSpecificOpsControlPointParser.parse(mCGMSpecificOpsControlPointCharacteristic) + "\" received");
super.onDataReceived(device, data);
}
@Override
public void onCGMSpecificOpsOperationCompleted(@NonNull final BluetoothDevice device, final int requestCode, final boolean secured) {
switch (requestCode) {
case CGM_OP_CODE_START_SESSION:
mSessionStartTime = System.currentTimeMillis();
log(LogContract.Log.Level.APPLICATION, "Session started");
break;
case CGM_OP_CODE_STOP_SESSION:
mSessionStartTime = 0;
log(LogContract.Log.Level.APPLICATION, "Session stopped");
break;
}
}
@Override
public void onCGMSpecificOpsOperationError(@NonNull final BluetoothDevice device, final int requestCode, final int error, final boolean secured) {
switch (requestCode) {
case CGM_OP_CODE_START_SESSION:
mSessionStartTime = 0;
log(LogContract.Log.Level.WARNING, "Session not started, error " + error);
break;
case CGM_OP_CODE_STOP_SESSION:
mSessionStartTime = 0;
log(LogContract.Log.Level.APPLICATION, "Session stopped, error " + error);
break;
}
}
@Override
public void onCGMSpecificOpsResponseReceivedWithCrcError(@NonNull final BluetoothDevice device, @NonNull final Data data) {
log(LogContract.Log.Level.ERROR, "Request failed: CRC error");
}
});
enableIndications(mRecordAccessControlPointCharacteristic)
.with(new RecordAccessControlPointDataCallback() {
@Override
public void onDataReceived(@NonNull final BluetoothDevice device, @NonNull final Data data) {
log(LogContract.Log.Level.APPLICATION, "\"" + RecordAccessControlPointParser.parse(mRecordAccessControlPointCharacteristic) + "\" received");
super.onDataReceived(device, data);
}
@Override
public void onRecordAccessOperationCompleted(@NonNull final BluetoothDevice device, final int requestCode) {
switch (requestCode) {
case RACP_OP_CODE_ABORT_OPERATION:
mCallbacks.onOperationAborted(device);
break;
default:
mCallbacks.onOperationCompleted(device);
break;
}
}
@Override
public void onRecordAccessOperationCompletedWithNoRecordsFound(@NonNull final BluetoothDevice device, final int requestCode) {
mCallbacks.onOperationCompleted(device);
}
@Override
public void onNumberOfRecordsReceived(@NonNull final BluetoothDevice device, final int numberOfRecords) {
mCallbacks.onNumberOfRecordsRequested(device, numberOfRecords);
if (numberOfRecords > 0) {
writeCharacteristic(mRecordAccessControlPointCharacteristic, RecordAccessControlPointData.reportNumberOfAllStoredRecords());
} else {
mCallbacks.onOperationCompleted(device);
}
}
@Override
public void onRecordAccessOperationError(@NonNull final BluetoothDevice device, final int requestCode, final int errorCode) {
log(LogContract.Log.Level.WARNING, "Record Access operation failed (error " + errorCode + ")");
if (errorCode == RACP_ERROR_OP_CODE_NOT_SUPPORTED) {
mCallbacks.onOperationNotSupported(device);
} else {
mCallbacks.onOperationFailed(device);
}
}
})
.fail((error) -> log(LogContract.Log.Level.WARNING, "Failed to enabled Record Access Control Point indications (error " + error + ")"));
// Start Continuous Glucose session
writeCharacteristic(mCGMSpecificOpsControlPointCharacteristic, CGMSpecificOpsControlPointData.startSession(mSecured))
.done(() -> log(LogContract.Log.Level.APPLICATION, "\"" + CGMSpecificOpsControlPointParser.parse(mCGMSpecificOpsControlPointCharacteristic) + "\" sent"))
.fail((error) -> log(LogContract.Log.Level.ERROR, "Failed to start session (error " + error + ")"));
} }
@Override @Override
protected boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) { protected boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) {
final BluetoothGattService service = gatt.getService(CGMS_UUID); final BluetoothGattService service = gatt.getService(CGMS_UUID);
if (service != null) { if (service != null) {
mCGMFeatureCharacteristic = service.getCharacteristic(CGM_FEAURE_UUID);
mCGMMeasurementCharacteristic = service.getCharacteristic(CGM_MEASUREMENT_UUID); mCGMMeasurementCharacteristic = service.getCharacteristic(CGM_MEASUREMENT_UUID);
mCGMOpsControlPointCharacteristic = service.getCharacteristic(CGM_OPS_CONTROL_POINT_UUID); mCGMSpecificOpsControlPointCharacteristic = service.getCharacteristic(CGM_OPS_CONTROL_POINT_UUID);
mRecordAccessControlPointCharacteristic = service.getCharacteristic(RACP_UUID); mRecordAccessControlPointCharacteristic = service.getCharacteristic(RACP_UUID);
} }
return mCGMMeasurementCharacteristic != null && mCGMOpsControlPointCharacteristic != null && mRecordAccessControlPointCharacteristic != null; return mCGMFeatureCharacteristic != null && mCGMMeasurementCharacteristic != null &&
} mCGMSpecificOpsControlPointCharacteristic != null && mRecordAccessControlPointCharacteristic != null;
@Override
protected boolean isOptionalServiceSupported(@NonNull final BluetoothGatt gatt) {
super.isOptionalServiceSupported(gatt); // ignore the result
final BluetoothGattService service = gatt.getService(CGMS_UUID);
if (service != null) {
mCGMOpsControlPointCharacteristic = service.getCharacteristic(CGM_OPS_CONTROL_POINT_UUID);
}
return mCGMOpsControlPointCharacteristic != null;
} }
@Override @Override
protected void onDeviceDisconnected() { protected void onDeviceDisconnected() {
super.onDeviceDisconnected(); super.onDeviceDisconnected();
mCGMOpsControlPointCharacteristic = null; mCGMFeatureCharacteristic = null;
mCGMMeasurementCharacteristic = null; mCGMMeasurementCharacteristic = null;
mCGMSpecificOpsControlPointCharacteristic = null;
mRecordAccessControlPointCharacteristic = null; mRecordAccessControlPointCharacteristic = null;
} }
@Override
protected void onCharacteristicWrite(@NonNull final BluetoothGatt gatt, @NonNull final BluetoothGattCharacteristic characteristic) {
if (characteristic.getUuid().equals(RACP_UUID)) {
log(LogContract.Log.Level.APPLICATION, "\"" + RecordAccessControlPointParser.parse(characteristic) + "\" sent");
} else { // uuid == CGM_OPS_CONTROL_POINT_UUID
log(LogContract.Log.Level.APPLICATION, "\"" + CGMSpecificOpsControlPointParser.parse(characteristic) + "\" sent");
}
}
@Override
public void onCharacteristicNotified(@NonNull final BluetoothGatt gatt, @NonNull final BluetoothGattCharacteristic characteristic) {
log(LogContract.Log.Level.APPLICATION, "\"" + CGMMeasurementParser.parse(characteristic) + "\" received");
// CGM Measurement characteristic may have one or more CGM records
int totalSize = characteristic.getValue().length;
int offset = 0;
while (offset < totalSize) {
final int cgmSize = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
final float cgmValue = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 2);
final int sequenceNumber = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset + 4);
final long timestamp = mSessionStartTime + (sequenceNumber * 60000L); // Sequence number is in minutes since Start Session
//This will send callback to CGMSActivity when new concentration value is received from CGMS device
final CGMSRecord cgmsRecord = new CGMSRecord(sequenceNumber, cgmValue, timestamp);
mRecords.put(cgmsRecord.sequenceNumber, cgmsRecord);
mCallbacks.onCGMValueReceived(gatt.getDevice(), cgmsRecord);
offset += cgmSize;
}
}
@Override
protected void onCharacteristicIndicated(@NonNull final BluetoothGatt gatt, @NonNull final BluetoothGattCharacteristic characteristic) {
if (characteristic.getUuid().equals(RACP_UUID)) {
log(LogContract.Log.Level.APPLICATION, "\"" + RecordAccessControlPointParser.parse(characteristic) + "\" received");
// 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(gatt.getDevice(), 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(gatt.getDevice());
}
} 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(gatt.getDevice());
else
mCallbacks.onOperationAborted(gatt.getDevice());
break;
case RESPONSE_NO_RECORDS_FOUND:
mCallbacks.onOperationCompleted(gatt.getDevice());
break;
case RESPONSE_OP_CODE_NOT_SUPPORTED:
mCallbacks.onOperationNotSupported(gatt.getDevice());
break;
case RESPONSE_PROCEDURE_NOT_COMPLETED:
case RESPONSE_ABORT_UNSUCCESSFUL:
default:
mCallbacks.onOperationFailed(gatt.getDevice());
break;
}
mAbort = false;
}
} else { // uuid == CGM_OPS_CONTROL_POINT_UUID
log(LogContract.Log.Level.APPLICATION, "\"" + CGMSpecificOpsControlPointParser.parse(characteristic) + "\" received");
}
}
}; };
/**
* Writes given operation parameters to the characteristic
*
* @param characteristic the characteristic to write. This must be the Record Access Control Point characteristic
* @param opCode the operation code
* @param operator the operator (see {@link #OPERATOR_NULL} and others
* @param params optional parameters (one for >=, <=, two for the range, none for other operators)
*/
private void setOpCode(final BluetoothGattCharacteristic characteristic, final int opCode, final int operator, final Integer... params) {
final int size = 2 + ((params.length > 0) ? 1 : 0) + params.length * 2; // 1 byte for opCode, 1 for operator, 1 for filter type (if parameters exists) and 2 for each parameter
characteristic.setValue(new byte[size]);
// write the operation code
int offset = 0;
characteristic.setValue(opCode, BluetoothGattCharacteristic.FORMAT_UINT8, offset);
offset += 1;
// write the operator. This is always present but may be equal to OPERATOR_NULL
characteristic.setValue(operator, BluetoothGattCharacteristic.FORMAT_UINT8, offset);
offset += 1;
// if parameters exists, append them. Parameters should be sorted from minimum to maximum. Currently only one or two params are allowed
if (params.length > 0) {
// our implementation use only sequence number as a filer type
characteristic.setValue(FILTER_TYPE_SEQUENCE_NUMBER, BluetoothGattCharacteristic.FORMAT_UINT8, offset);
offset += 1;
for (final Integer i : params) {
characteristic.setValue(i, BluetoothGattCharacteristic.FORMAT_UINT16, offset);
offset += 2;
}
}
}
/** /**
* Returns a list of CGM records obtained from this device. The key in the array is the * Returns a list of CGM records obtained from this device. The key in the array is the
*/ */
@@ -288,12 +244,12 @@ public class CGMSManager extends BatteryManager<CGMSManagerCallbacks> {
*/ */
public void clear() { public void clear() {
mRecords.clear(); mRecords.clear();
mCallbacks.onDatasetClear(getBluetoothDevice()); mCallbacks.onDatasetCleared(getBluetoothDevice());
} }
/** /**
* 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 * 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. * Control Point indication with status code Success or other in case of error.
*/ */
public void getLastRecord() { public void getLastRecord() {
if (mRecordAccessControlPointCharacteristic == null) if (mRecordAccessControlPointCharacteristic == null)
@@ -301,15 +257,13 @@ public class CGMSManager extends BatteryManager<CGMSManagerCallbacks> {
clear(); clear();
mCallbacks.onOperationStarted(getBluetoothDevice()); mCallbacks.onOperationStarted(getBluetoothDevice());
writeCharacteristic(mRecordAccessControlPointCharacteristic, RecordAccessControlPointData.reportLastStoredRecord());
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 * Sends the request to obtain the first (oldest) record from glucose device.
* Point indication with status code ({@link #RESPONSE_SUCCESS} or other in case of error. * The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access Control
* Point indication with status code Success or other in case of error.
*/ */
public void getFirstRecord() { public void getFirstRecord() {
if (mRecordAccessControlPointCharacteristic == null) if (mRecordAccessControlPointCharacteristic == null)
@@ -317,29 +271,25 @@ public class CGMSManager extends BatteryManager<CGMSManagerCallbacks> {
clear(); clear();
mCallbacks.onOperationStarted(getBluetoothDevice()); mCallbacks.onOperationStarted(getBluetoothDevice());
writeCharacteristic(mRecordAccessControlPointCharacteristic, RecordAccessControlPointData.reportFirstStoredRecord());
final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic;
setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_FIRST_RECORD);
writeCharacteristic(characteristic);
} }
/** /**
* Sends abort operation signal to the device * Sends abort operation signal to the device.
*/ */
public void abort() { public void abort() {
if (mRecordAccessControlPointCharacteristic == null) if (mRecordAccessControlPointCharacteristic == null)
return; return;
mAbort = true; mAbort = true;
final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic; writeCharacteristic(mRecordAccessControlPointCharacteristic, RecordAccessControlPointData.abortOperation());
setOpCode(characteristic, OP_CODE_ABORT_OPERATION, OPERATOR_NULL);
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 * Sends the request to obtain all records from glucose device. Initially we want to notify the user
* 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 * about the number of the records so the Report Number of Stored Records request is send. The
* error. * data will be returned to Glucose Measurement characteristic as a notification followed by
* Record Access Control Point indication with status code Success or other in case of error.
*/ */
public void getAllRecords() { public void getAllRecords() {
if (mRecordAccessControlPointCharacteristic == null) if (mRecordAccessControlPointCharacteristic == null)
@@ -347,16 +297,14 @@ public class CGMSManager extends BatteryManager<CGMSManagerCallbacks> {
clear(); clear();
mCallbacks.onOperationStarted(getBluetoothDevice()); mCallbacks.onOperationStarted(getBluetoothDevice());
writeCharacteristic(mRecordAccessControlPointCharacteristic, RecordAccessControlPointData.reportNumberOfAllStoredRecords());
final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic;
setOpCode(characteristic, OP_CODE_REPORT_NUMBER_OF_RECORDS, OPERATOR_ALL_RECORDS);
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 * Sends the request to obtain all records from glucose device. Initially we want to notify the user
* 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 * about the number of the records so the Report Number of Stored Records request is send. The
* error. * data will be returned to Glucose Measurement characteristic as a notification followed by
* Record Access Control Point indication with status code Success or other in case of error.
*/ */
public void refreshRecords() { public void refreshRecords() {
if (mRecordAccessControlPointCharacteristic == null) if (mRecordAccessControlPointCharacteristic == null)
@@ -367,28 +315,27 @@ public class CGMSManager extends BatteryManager<CGMSManagerCallbacks> {
} else { } else {
mCallbacks.onOperationStarted(getBluetoothDevice()); mCallbacks.onOperationStarted(getBluetoothDevice());
// obtain the last sequence number // Obtain the last sequence number
final int sequenceNumber = mRecords.keyAt(mRecords.size() - 1) + 1; final int sequenceNumber = mRecords.keyAt(mRecords.size() - 1) + 1;
writeCharacteristic(mRecordAccessControlPointCharacteristic, RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo(sequenceNumber));
final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic;
setOpCode(characteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_GREATER_THEN_OR_EQUAL, sequenceNumber);
writeCharacteristic(characteristic);
// Info: // Info:
// Operators OPERATOR_GREATER_THEN_OR_EQUAL, OPERATOR_LESS_THEN_OR_EQUAL and OPERATOR_RANGE are not supported by the CGMS sample from SDK // Operators OPERATOR_GREATER_THEN_OR_EQUAL, OPERATOR_LESS_THEN_OR_EQUAL and OPERATOR_RANGE are not supported by the CGMS sample from SDK
// The "Operation not supported" response will be received // The "Operation not supported" response will be received
} }
} }
/**
* Sends the request to remove all stored records from the Continuous Glucose Monitor device.
* This feature is not supported by the CGMS sample from the SDK, so monitor will answer with
* the Op Code Not Supported error.
*/
public void deleteAllRecords() { public void deleteAllRecords() {
if (mRecordAccessControlPointCharacteristic == null) if (mRecordAccessControlPointCharacteristic == null)
return; return;
clear(); clear();
mCallbacks.onOperationStarted(getBluetoothDevice()); mCallbacks.onOperationStarted(getBluetoothDevice());
writeCharacteristic(mRecordAccessControlPointCharacteristic, RecordAccessControlPointData.deleteAllStoredRecords());
final BluetoothGattCharacteristic characteristic = mRecordAccessControlPointCharacteristic;
setOpCode(characteristic, OP_CODE_DELETE_STORED_RECORDS, OPERATOR_ALL_RECORDS);
writeCharacteristic(characteristic);
} }
} }

View File

@@ -25,14 +25,11 @@ package no.nordicsemi.android.nrftoolbox.cgms;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import no.nordicsemi.android.ble.BleManagerCallbacks;
import no.nordicsemi.android.nrftoolbox.battery.BatteryManagerCallbacks; import no.nordicsemi.android.nrftoolbox.battery.BatteryManagerCallbacks;
public interface CGMSManagerCallbacks extends BatteryManagerCallbacks { public interface CGMSManagerCallbacks extends BatteryManagerCallbacks {
/**
* Called when new CGM value has been obtained from the sensor. void onCGMValueReceived(@NonNull final BluetoothDevice device, final CGMSRecord record);
*/
void onCGMValueReceived(final @NonNull BluetoothDevice device, final CGMSRecord record);
void onOperationStarted(final @NonNull BluetoothDevice device); void onOperationStarted(final @NonNull BluetoothDevice device);
@@ -44,7 +41,7 @@ public interface CGMSManagerCallbacks extends BatteryManagerCallbacks {
void onOperationNotSupported(final @NonNull BluetoothDevice device); void onOperationNotSupported(final @NonNull BluetoothDevice device);
void onDatasetClear(final @NonNull BluetoothDevice device); void onDatasetCleared(final @NonNull BluetoothDevice device);
void onNumberOfRecordsRequested(final @NonNull BluetoothDevice device, final int value); void onNumberOfRecordsRequested(final @NonNull BluetoothDevice device, final int value);

View File

@@ -3,7 +3,7 @@ package no.nordicsemi.android.nrftoolbox.cgms;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
public class CGMSRecord implements Parcelable{ class CGMSRecord implements Parcelable{
/** Record sequence number. */ /** Record sequence number. */
protected int sequenceNumber; protected int sequenceNumber;
/** The base time of the measurement (start time + sequenceNumber of minutes). */ /** The base time of the measurement (start time + sequenceNumber of minutes). */
@@ -11,13 +11,13 @@ public class CGMSRecord implements Parcelable{
/** The glucose concentration in mg/dL. */ /** The glucose concentration in mg/dL. */
protected float glucoseConcentration; protected float glucoseConcentration;
protected CGMSRecord(int sequenceNumber, float glucoseConcentration, long timestamp) { CGMSRecord(final int sequenceNumber, final float glucoseConcentration, final long timestamp) {
this.sequenceNumber = sequenceNumber; this.sequenceNumber = sequenceNumber;
this.glucoseConcentration = glucoseConcentration; this.glucoseConcentration = glucoseConcentration;
this.timestamp = timestamp; this.timestamp = timestamp;
} }
protected CGMSRecord(Parcel in) { private CGMSRecord(final Parcel in) {
this.sequenceNumber = in.readInt(); this.sequenceNumber = in.readInt();
this.glucoseConcentration = in.readFloat(); this.glucoseConcentration = in.readFloat();
this.timestamp = in.readLong(); this.timestamp = in.readLong();
@@ -25,12 +25,12 @@ public class CGMSRecord implements Parcelable{
public static final Creator<CGMSRecord> CREATOR = new Creator<CGMSRecord>() { public static final Creator<CGMSRecord> CREATOR = new Creator<CGMSRecord>() {
@Override @Override
public CGMSRecord createFromParcel(Parcel in) { public CGMSRecord createFromParcel(final Parcel in) {
return new CGMSRecord(in); return new CGMSRecord(in);
} }
@Override @Override
public CGMSRecord[] newArray(int size) { public CGMSRecord[] newArray(final int size) {
return new CGMSRecord[size]; return new CGMSRecord[size];
} }
}; };
@@ -41,7 +41,7 @@ public class CGMSRecord implements Parcelable{
} }
@Override @Override
public void writeToParcel(Parcel parcel, int i) { public void writeToParcel(final Parcel parcel, final int flags) {
parcel.writeInt(sequenceNumber); parcel.writeInt(sequenceNumber);
parcel.writeFloat(glucoseConcentration); parcel.writeFloat(glucoseConcentration);
parcel.writeLong(timestamp); parcel.writeLong(timestamp);

View File

@@ -9,11 +9,13 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.util.SparseArray; import android.util.SparseArray;
import no.nordicsemi.android.ble.BleManager; import no.nordicsemi.android.ble.BleManager;
import no.nordicsemi.android.ble.common.profile.cgm.CGMTypes;
import no.nordicsemi.android.log.Logger; import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.FeaturesActivity; import no.nordicsemi.android.nrftoolbox.FeaturesActivity;
import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.R;
@@ -72,35 +74,36 @@ public class CGMService extends BleProfileService implements CGMSManagerCallback
* Point indication with status code ({@link CGMSManager# RESPONSE_SUCCESS} or other in case of error. * Point indication with status code ({@link CGMSManager# RESPONSE_SUCCESS} or other in case of error.
*/ */
public void getFirstRecord() { public void getFirstRecord() {
if(mManager != null) if (mManager != null)
mManager.getFirstRecord(); mManager.getFirstRecord();
} }
/** /**
* Sends the request to obtain the last (most recent) record from glucose device. * 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 * The data will be returned to Glucose Measurement characteristic as a notification followed by Record Access
* Control Point indication with status code ({@link CGMSManager#RESPONSE_SUCCESS} or other in case of error. * Control Point indication with status code Success or other in case of error.
*/ */
public void getLastRecord() { public void getLastRecord() {
if(mManager != null) if (mManager != null)
mManager.getLastRecord(); mManager.getLastRecord();
} }
/** /**
* Sends the request to obtain all records from glucose device. * Sends the request to obtain all records from glucose device.
* Initially we want to notify user about the number of the records so the {@link CGMSManager#OP_CODE_REPORT_NUMBER_OF_RECORDS} is send. * Initially we want to notify user about the number of the records so the Report Number of Stored Records is send.
* The data will be returned to Glucose Measurement characteristic as a series of notifications followed by Record Access Control Point * The data will be returned to Glucose Measurement characteristic as a series of notifications followed
* indication with status code ({@link CGMSManager#RESPONSE_SUCCESS} or other in case of error. * by Record Access Control Point indication with status code Success or other in case of error.
*/ */
public void getAllRecords() { public void getAllRecords() {
if(mManager != null) if (mManager != null)
mManager.getAllRecords(); mManager.getAllRecords();
} }
/** /**
* Sends the request to obtain all records from glucose device with sequence number greater than the last one already obtained. * Sends the request to obtain all records from glucose device with sequence number greater
* The data will be returned to Glucose Measurement characteristic as a series of notifications followed by Record Access Control Point * than the last one already obtained. The data will be returned to Glucose Measurement
* indication with status code ({@link CGMSManager#RESPONSE_SUCCESS} or other in case of error. * characteristic as a series of notifications followed by Record Access Control Point
* indication with status code Success or other in case of error.
*/ */
public void refreshRecords() { public void refreshRecords() {
if (mManager != null) if (mManager != null)
@@ -111,7 +114,7 @@ public class CGMService extends BleProfileService implements CGMSManagerCallback
* Sends abort operation signal to the device * Sends abort operation signal to the device
*/ */
public void abort() { public void abort() {
if(mManager != null) if (mManager != null)
mManager.abort(); mManager.abort();
} }
@@ -119,7 +122,7 @@ public class CGMService extends BleProfileService implements CGMSManagerCallback
* Sends Delete op code with All stored records parameter. This method may not be supported by the SDK sample. * Sends Delete op code with All stored records parameter. This method may not be supported by the SDK sample.
*/ */
public void deleteAllRecords() { public void deleteAllRecords() {
if(mManager != null) if (mManager != null)
mManager.deleteAllRecords(); mManager.deleteAllRecords();
} }
} }
@@ -167,11 +170,9 @@ public class CGMService extends BleProfileService implements CGMSManagerCallback
/** /**
* Creates the notification * Creates the notification
* *
* @param messageResId * @param messageResId the message resource id. The message must have one String parameter,<br />
* the 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 signals that will be used to notify the user
* signals that will be used to notify the user
*/ */
private void createNotification(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);
@@ -182,7 +183,7 @@ public class CGMService extends BleProfileService implements CGMSManagerCallback
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);
// both activities above have launchMode="singleTask" in the AndroidManifest.xml file, so if the task is already running, it will be resumed // 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); final NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ToolboxApplication.CONNECTED_DEVICE_CHANNEL);
builder.setContentIntent(pendingIntent); builder.setContentIntent(pendingIntent);
builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName())); builder.setContentTitle(getString(R.string.app_name)).setContentText(getString(messageResId, getDeviceName()));
@@ -266,14 +267,14 @@ public class CGMService extends BleProfileService implements CGMSManagerCallback
} }
@Override @Override
public void onDatasetClear(@NonNull final BluetoothDevice device) { public void onDatasetCleared(@NonNull final BluetoothDevice device) {
final Intent broadcast = new Intent(BROADCAST_DATA_SET_CLEAR); final Intent broadcast = new Intent(BROADCAST_DATA_SET_CLEAR);
broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice()); broadcast.putExtra(EXTRA_DEVICE, getBluetoothDevice());
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast); LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
} }
@Override @Override
public void onNumberOfRecordsRequested(@NonNull final BluetoothDevice device, int value) { public void onNumberOfRecordsRequested(@NonNull final BluetoothDevice device, final int value) {
showToast(getResources().getQuantityString(R.plurals.gls_progress, value, value)); showToast(getResources().getQuantityString(R.plurals.gls_progress, value, value));
} }