@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 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); } } }
@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); } }
/** * 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); }
/** * Disconnects from the device. Does nothing if not connected. * * @return true if device is to be disconnected. False if it was already disconnected. */ public boolean disconnect() { mUserDisconnected = true; if (mConnected && mBluetoothGatt != null) { Logger.v(mLogSession, "Disconnecting..."); mCallbacks.onDeviceDisconnecting(); Logger.d(mLogSession, "gatt.disconnect()"); mBluetoothGatt.disconnect(); return true; } return false; }
/** * 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); }
@Override public void onReceive(final Context context, final Intent intent) { final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // Skip other devices if (mBluetoothGatt == null || !device.getAddress().equals(mBluetoothGatt.getDevice().getAddress())) return; // String values are used as the constants are not available for Android 4.3. final int variant = intent.getIntExtra( "android.bluetooth.device.extra.PAIRING_VARIANT" /*BluetoothDevice.EXTRA_PAIRING_VARIANT*/, 0); Logger.d( mLogSession, "[Broadcast] Action received: android.bluetooth.device.action.PAIRING_REQUEST" /*BluetoothDevice.ACTION_PAIRING_REQUEST*/ + ", pairing variant: " + pairingVariantToString(variant) + " (" + variant + ")"); // The API below is available for Android 4.4 or newer. // An app may set the PIN here or set pairing confirmation (depending on the variant) // using: // device.setPin(new byte[] { '1', '2', '3', '4', '5', '6' }); // device.setPairingConfirmation(true); }
@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 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; Logger.d( mLogSession, "[Broadcast] Action received: " + BluetoothDevice.ACTION_BOND_STATE_CHANGED + ", bond state changed to: " + bondStateToString(bondState) + " (" + bondState + ")"); DebugLogger.i( TAG, "Bond state changed for: " + device.getName() + " new state: " + bondState + " previous: " + previousBondState); switch (bondState) { case BluetoothDevice.BOND_BONDING: mCallbacks.onBondingRequired(); break; case BluetoothDevice.BOND_BONDED: Logger.i(mLogSession, "Device 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; } }
/** * Connects to the Bluetooth Smart device * * @param device a device to connect to */ 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()); }
@Override protected void onCharacteristicIndicated( final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { if (mLogSession != null) Logger.a(mLogSession, RecordAccessControlPointParser.parse(characteristic)); // 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; } }
@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); } }
/** * 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; }
/** * 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); }
/** * 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); }
@Override public void onCharacteristicNotified( final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { if (mLogSession != null) Logger.a(mLogSession, CSCMeasurementParser.parse(characteristic)); // Decode the new data int offset = 0; final int flags = characteristic.getValue()[offset]; // 1 byte offset += 1; final boolean wheelRevPresent = (flags & WHEEL_REVOLUTIONS_DATA_PRESENT) > 0; final boolean crankRevPreset = (flags & CRANK_REVOLUTION_DATA_PRESENT) > 0; if (wheelRevPresent) { final int wheelRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, offset); offset += 4; final int lastWheelEventTime = characteristic.getIntValue( BluetoothGattCharacteristic.FORMAT_UINT16, offset); // 1/1024 s offset += 2; // Notify listener about the new measurement mCallbacks.onWheelMeasurementReceived(wheelRevolutions, lastWheelEventTime); } if (crankRevPreset) { final int crankRevolutions = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); offset += 2; final int lastCrankEventTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); // offset += 2; // Notify listener about the new measurement mCallbacks.onCrankMeasurementReceived(crankRevolutions, lastCrankEventTime); } }
/** * 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; }
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 void onCharacteristicNotified( BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { final UUID uuid = characteristic.getUuid(); if (GM_CHARACTERISTIC.equals(uuid)) { if (mLogSession != null) Logger.a(mLogSession, GlucoseMeasurementParser.parse(characteristic)); int offset = 0; final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); offset += 1; final boolean timeOffsetPresent = (flags & 0x01) > 0; final boolean typeAndLocationPresent = (flags & 0x02) > 0; final int concentrationUnit = (flags & 0x04) > 0 ? GlucoseRecord.UNIT_molpl : GlucoseRecord.UNIT_kgpl; final boolean sensorStatusAnnunciationPresent = (flags & 0x08) > 0; final boolean contextInfoFollows = (flags & 0x10) > 0; // create and fill the new record final GlucoseRecord record = new GlucoseRecord(); record.sequenceNumber = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); offset += 2; final int year = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); final int month = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2) - 1; // months are 1-based 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); offset += 7; final Calendar calendar = Calendar.getInstance(); calendar.set(year, month, day, hours, minutes, seconds); record.time = calendar; if (timeOffsetPresent) { // time offset is ignored in the current release record.timeOffset = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, offset); offset += 2; } if (typeAndLocationPresent) { record.glucoseConcentration = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); record.unit = concentrationUnit; final int typeAndLocation = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2); record.type = (typeAndLocation & 0xF0) >> 4; // TODO this way or around? record.sampleLocation = (typeAndLocation & 0x0F); offset += 3; } if (sensorStatusAnnunciationPresent) { record.status = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); } // This allows you to check other values that are not provided by the Nordic // Semiconductor's Glucose Service in SDK 4.4.2. // record.status = 0x1A; // record.context = new GlucoseRecord.MeasurementContext(); // record.context.carbohydrateId = 1; // record.context.carbohydrateUnits = 0.23f; // record.context.meal = 2; // record.context.tester = 2; // record.context.health = 4; // the following values are not implemented yet (see // ExpandableRecordAdapter#getChildrenCount() and #getChild(...) // record.context.exerciseDuration = 3600; // record.context.exerciseIntensity = 45; // record.context.medicationId = 3; // record.context.medicationQuantity = 0.03f; // record.context.medicationUnit = GlucoseRecord.MeasurementContext.UNIT_kg; // record.context.HbA1c = 213.3f; // data set modifications must be done in UI thread mHandler.post( new Runnable() { @Override public void run() { // insert the new record to storage mRecords.put(record.sequenceNumber, record); // if there is no context information following the measurement data, notify // callback about the new record if (!contextInfoFollows) mCallbacks.onDatasetChanged(); } }); } else if (GM_CONTEXT_CHARACTERISTIC.equals(uuid)) { if (mLogSession != null) Logger.a(mLogSession, GlucoseMeasurementContextParser.parse(characteristic)); int offset = 0; final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); offset += 1; 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 ? GlucoseRecord.MeasurementContext.UNIT_l : GlucoseRecord.MeasurementContext.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; final GlucoseRecord record = mRecords.get(sequenceNumber); if (record == null) { DebugLogger.w( TAG, "Context information with unknown sequence number: " + sequenceNumber); return; } final GlucoseRecord.MeasurementContext context = new GlucoseRecord.MeasurementContext(); record.context = context; if (moreFlagsPresent) offset += 1; if (carbohydratePresent) { context.carbohydrateId = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); context.carbohydrateUnits = characteristic.getFloatValue( BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 1); offset += 3; } if (mealPresent) { context.meal = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); offset += 1; } if (testerHealthPresent) { final int testerHealth = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); context.tester = (testerHealth & 0xF0) >> 4; context.health = (testerHealth & 0x0F); offset += 1; } if (exercisePresent) { context.exerciseDuration = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); context.exerciseIntensity = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2); offset += 3; } if (medicationPresent) { context.medicationId = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset); context.medicationQuantity = characteristic.getFloatValue( BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 1); context.medicationUnit = medicationUnit; offset += 3; } if (hbA1cPresent) { context.HbA1c = characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset); } // notify callback about the new record mCallbacks.onDatasetChanged(); } }
@Override public final void onConnectionStateChange( final BluetoothGatt gatt, final int status, final int newState) { Logger.d( 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); } }