/** * Sends the given text to RX characteristic. * @param text the text to be sent */ public void send(final String text) { // Are we connected? if (mRXCharacteristic == null) return; if (!TextUtils.isEmpty(text)) { final WriteRequest request = writeCharacteristic(mRXCharacteristic, text.getBytes()) .with((device, data) -> log(LogContract.Log.Level.APPLICATION, "\"" + data.getStringValue(0) + "\" sent")); if (!mUseLongWrite) { // This will automatically split the long data into MTU-3-byte long packets. request.split(); } request.enqueue(); } } }
/** * Creates new Enable Indications on Service Changed characteristic. It is a NOOP if such * characteristic does not exist in the Generic Attribute service. * It is required to enable those notifications on bonded devices on older Android versions to * be informed about attributes changes. * Android 7+ (or 6+) handles this automatically and no action is required. * * @return The new request. */ @NonNull static WriteRequest newEnableServiceChangedIndicationsRequest() { return new WriteRequest(Type.ENABLE_SERVICE_CHANGED_INDICATIONS); }
/** * This method disables notifications on the Battery Level characteristic. * * @deprecated Use {@link #disableNotifications(BluetoothGattCharacteristic)} instead. */ @Deprecated protected void disableBatteryLevelNotifications() { Request.newDisableBatteryLevelNotificationsRequest().setManager(this) .done(device -> log(Log.INFO, "Battery Level notifications disabled")) .enqueue(); }
/** * This method will write important data to the device. * * @param parameter parameter to be written. */ void performAction(final String parameter) { log(Log.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 in case // Write Without Request type was used. .with((device, data) -> log(Log.DEBUG, data.size() + " bytes were sent")) // Callback called when data were sent, or added to outgoing queue in case // Write Without Request type was used. This is called after .with(...) callback. .done(device -> log(LogContract.Log.Level.APPLICATION, "Device name set to \"" + parameter + "\"")) // Callback called when write has failed. .fail((device, status) -> log(Log.WARN, "Failed to change device name")) .enqueue(); } }
.split((device, data, index) -> { assertArrayEquals(chunk, data); assertTrue(data.length <= MTU - 3); }).done(device -> done = true); chunk = request.getData(MTU); if (chunk != null) { request.notifyPacketSent(null, chunk); assertTrue(called); } else { } while (request.hasMore()); request.notifySuccess(null);
/** * Sends abort operation signal to the device. */ public void abort() { if (mRecordAccessControlPointCharacteristic == null) return; writeCharacteristic(mRecordAccessControlPointCharacteristic, RecordAccessControlPointData.abortOperation()) .with((device, data) -> log(LogContract.Log.Level.APPLICATION, "\"" + RecordAccessControlPointParser.parse(data) + "\" sent")) .enqueue(); }
.split(); chunk = request.getData(MTU); if (chunk != null) { request.notifyPacketSent(null, chunk); } while (request.hasMore());
@Test public void split_highMtu() { final int MTU_HIGH = 276; final WriteRequest request = Request.newWriteRequest(characteristic, text.getBytes(), BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) .split(); chunk = request.getData(MTU_HIGH); // Verify the chunk assertNotNull(chunk); assertEquals(MTU_HIGH - 3, chunk.length); final String expected = text.substring(0, MTU_HIGH - 3); assertArrayEquals(expected.getBytes(), chunk); }
@Override protected void initialize() { super.initialize(); setIndicationCallback(mHTCharacteristic) .with(new TemperatureMeasurementDataCallback() { @Override public void onDataReceived(@NonNull final BluetoothDevice device, @NonNull final Data data) { log(LogContract.Log.Level.APPLICATION, "\"" + TemperatureMeasurementParser.parse(data) + "\" received"); super.onDataReceived(device, data); } @Override public void onTemperatureMeasurementReceived(@NonNull final BluetoothDevice device, final float temperature, final int unit, @Nullable final Calendar calendar, @Nullable final Integer type) { mCallbacks.onTemperatureMeasurementReceived(device, temperature, unit, calendar, type); } }); enableIndications(mHTCharacteristic).enqueue(); }
.fail((device, status) -> log(Log.WARN, "Failed to enable Continuous Glucose Measurement notifications (" + status + ")")) .enqueue(); enableIndications(mCGMSpecificOpsControlPointCharacteristic) .fail((device, status) -> log(Log.WARN, "Failed to enable CGM Specific Ops Control Point indications notifications (" + status + ")")) .enqueue(); enableIndications(mRecordAccessControlPointCharacteristic) .fail((device, status) -> log(Log.WARN, "Failed to enabled Record Access Control Point indications (error " + status + ")")) .enqueue(); .with((device, data) -> log(LogContract.Log.Level.APPLICATION, "\"" + CGMSpecificOpsControlPointParser.parse(data) + "\" sent")) .fail((device, status) -> log(LogContract.Log.Level.ERROR, "Failed to start session (error " + status + ")")) .enqueue();
/** * This method makes sure the data sent will be split to at-most MTU-3 bytes long packets. * This is because Long Write does not work with Reliable Write. */ void forceSplit() { if (dataSplitter == null) split(); }
WriteRequest(@NonNull final Type type, @Nullable final BluetoothGattDescriptor descriptor, @Nullable final byte[] data, @IntRange(from = 0) final int offset, @IntRange(from = 0) final int length) { super(type, descriptor); this.data = copy(data, offset, length); this.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT; }
@NonNull @Override public ReliableWriteRequest add(@NonNull final Operation operation) { super.add(operation); // Make sure the write request uses splitting, as Long Write is not supported // in Reliable Write sub-procedure. if (operation instanceof WriteRequest) { ((WriteRequest) operation).forceSplit(); } return this; }
/** * Writes the HIGH ALERT or NO ALERT command to the target device. * * @param on true to enable the alarm on proximity tag, false to disable it. */ public void writeImmediateAlert(final boolean on) { if (!isConnected()) return; writeCharacteristic(mAlertLevelCharacteristic, on ? AlertLevelData.highAlert() : AlertLevelData.noAlert()) .before(device -> log(Log.VERBOSE, on ? "Setting alarm to HIGH..." : "Disabling alarm...")) .with((device, data) -> log(LogContract.Log.Level.APPLICATION, "\"" + AlertLevelParser.parse(data) + "\" sent")) .done(device -> { mAlertOn = on; mCallbacks.onRemoteAlarmSwitched(device, on); }) .fail((device, status) -> log(Log.WARN, status == FailCallback.REASON_NULL_ATTRIBUTE ? "Alert Level characteristic not found" : GattError.parse(status))) .enqueue(); }
/** * Sends abort operation signal to the device. */ public void abort() { if (mRecordAccessControlPointCharacteristic == null) return; writeCharacteristic(mRecordAccessControlPointCharacteristic, RecordAccessControlPointData.abortOperation()) .with((device, data) -> log(LogContract.Log.Level.APPLICATION, "\"" + RecordAccessControlPointParser.parse(data) + "\" sent")) .enqueue(); }
/** * This method enables notifications on the Battery Level characteristic. * * @deprecated Use {@link #setNotificationCallback(BluetoothGattCharacteristic)} and * {@link #enableNotifications(BluetoothGattCharacteristic)} instead. */ @SuppressWarnings("ConstantConditions") @Deprecated protected void enableBatteryLevelNotifications() { if (mBatteryLevelNotificationCallback == null) { mBatteryLevelNotificationCallback = new ValueChangedCallback() .with((device, data) -> { if (data.size() == 1) { final int batteryLevel = data.getIntValue(Data.FORMAT_UINT8, 0); mBatteryValue = batteryLevel; final BleManagerGattCallback callback = mGattCallback; if (callback != null) { callback.onBatteryValueReceived(mBluetoothGatt, batteryLevel); } mCallbacks.onBatteryValueReceived(device, batteryLevel); } }); } Request.newEnableBatteryLevelNotificationsRequest().setManager(this) .done(device -> log(Log.INFO, "Battery Level notifications enabled")) .enqueue(); }
@Test public void split_basic() { final WriteRequest request = Request.newWriteRequest(characteristic, text.getBytes(), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) .split(); chunk = request.getData(MTU); // Verify the chunk assertNotNull(chunk); assertEquals(MTU - 3, chunk.length); final String expected = text.substring(0, MTU - 3); assertArrayEquals(expected.getBytes(), chunk); }