Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## 2.1.0
* Add optional `queueId` parameter to all APIs
* Android: add `legacy` to `AndroidOptions` — set `legacy: true` to scan legacy BLE 4.x advertisements (e.g. ESP32) on API 26+; default (`null`/`false`) keeps extended-advertisement scanning unchanged from prior releases

## 2.0.4
Expand Down
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,13 @@ If you want to parallelize commands between multiple devices, you can set:
UniversalBle.queueType = QueueType.perDevice;
```

You can have separate queues by passing an optional `queueId`. Commands with the same `queueId` are serialized together, but run in parallel with both `QueueType.perDevice` and `QueueType.global`:

```dart
UniversalBle.write(deviceId, service, char, value1, queueId: '1');
UniversalBle.write(deviceId, service, char, value2, queueId: '2');
```

You can also completely disable the queue and batch all commands, even for the same device, by using:

```dart
Expand All @@ -575,13 +582,20 @@ UniversalBle.onQueueUpdate = (String id, int remainingItems) {
};
```

To clear the queue:
To clear a queue:

```dart
/// Use [BleCommandQueue.globalQueueId] to clear the global queue.
/// To clear the queue of a specific device, use `deviceId` as [id].
/// If no [id] is provided, all queues will be cleared.
UniversalBle.clearQueue(BleCommandQueue.globalQueueId);
// Clear global queue
UniversalBle.clearQueue(BleCommandQueue.globalQueueId);

// Clear a per-device queue (when queueType is perDevice)
UniversalBle.clearQueue(deviceId);

// Clear a custom queue (same string passed as queueId to read/write/etc.)
UniversalBle.clearQueue('customQueueId');

// Clear all queues
UniversalBle.clearQueue();
```

## Timeout
Expand Down Expand Up @@ -1025,6 +1039,7 @@ To opt in, declare the `Uses Bluetooth LE accessories` background mode. After en
...
</array>
```

Notes:

- Without the `bluetooth-central` background mode, `CBCentralManager` is created lazily on the first central BLE API call and state restoration is disabled.
Expand Down
2 changes: 1 addition & 1 deletion example/macos/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
universal_ble: a322ebabee64f0ec27a313e89a5dd6967f37a60f
universal_ble: d0c3d7347d16c82746288e13ed2f1368ecf8e716

PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009

Expand Down
31 changes: 24 additions & 7 deletions lib/src/extensions/ble_characteristic_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,24 @@ extension BleCharacteristicExtension on BleCharacteristic {
CharacteristicSubscription(this, CharacteristicProperty.indicate);

/// Unsubscribes notifications/indications from this characteristic.
Future<void> unsubscribe({Duration? timeout}) =>
UniversalBle.unsubscribe(_deviceId, _serviceId, uuid, timeout: timeout);
Future<void> unsubscribe({Duration? timeout, String? queueId}) =>
UniversalBle.unsubscribe(
_deviceId,
_serviceId,
uuid,
timeout: timeout,
queueId: queueId,
);

/// Reads the current value of the characteristic.
Future<Uint8List> read({Duration? timeout}) =>
UniversalBle.read(_deviceId, _serviceId, uuid, timeout: timeout);
Future<Uint8List> read({Duration? timeout, String? queueId}) =>
UniversalBle.read(
_deviceId,
_serviceId,
uuid,
timeout: timeout,
queueId: queueId,
);

/// Writes a value to the characteristic.
///
Expand All @@ -38,6 +50,7 @@ extension BleCharacteristicExtension on BleCharacteristic {
List<int> value, {
bool withResponse = true,
Duration? timeout,
String? queueId,
}) async {
await UniversalBle.write(
_deviceId,
Expand All @@ -46,6 +59,7 @@ extension BleCharacteristicExtension on BleCharacteristic {
Uint8List.fromList(value),
withoutResponse: !withResponse,
timeout: timeout,
queueId: queueId,
);
}

Expand Down Expand Up @@ -85,7 +99,7 @@ class CharacteristicSubscription {
final bool isSupported;

CharacteristicSubscription(this._characteristic, this._property)
: isSupported = _characteristic.properties.contains(_property);
: isSupported = _characteristic.properties.contains(_property);

/// Registers a listener for incoming data from the characteristic.
StreamSubscription listen(
Expand All @@ -103,7 +117,7 @@ class CharacteristicSubscription {
}

/// Subscribes to this characteristic.
Future<void> subscribe({Duration? timeout}) {
Future<void> subscribe({Duration? timeout, String? queueId}) {
if (!isSupported) throw Exception('Operation not supported');

if (_property == CharacteristicProperty.indicate) {
Expand All @@ -112,6 +126,7 @@ class CharacteristicSubscription {
_characteristic._serviceId,
_characteristic.uuid,
timeout: timeout,
queueId: queueId,
);
}

Expand All @@ -120,17 +135,19 @@ class CharacteristicSubscription {
_characteristic._serviceId,
_characteristic.uuid,
timeout: timeout,
queueId: queueId,
);
}

/// Unsubscribes from this characteristic.
Future<void> unsubscribe({Duration? timeout}) {
Future<void> unsubscribe({Duration? timeout, String? queueId}) {
if (!isSupported) throw Exception('Operation not supported');
return UniversalBle.unsubscribe(
_characteristic._deviceId,
_characteristic._serviceId,
_characteristic.uuid,
timeout: timeout,
queueId: queueId,
);
}

Expand Down
35 changes: 27 additions & 8 deletions lib/src/extensions/ble_device_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ extension BleDeviceExtension on BleDevice {
);

/// Disconnects from the device.
Future<void> disconnect() => UniversalBle.disconnect(deviceId);
Future<void> disconnect({Duration? timeout, String? queueId}) =>
UniversalBle.disconnect(deviceId, timeout: timeout, queueId: queueId);

/// Requests an MTU (Maximum Transmission Unit) value for the connection.
///
Expand All @@ -34,20 +35,25 @@ extension BleDeviceExtension on BleDevice {
/// which may differ from `expectedMtu`.
///
/// See [UniversalBle.requestMtu] for platform limitations and best practices.
Future<int> requestMtu(int expectedMtu) =>
UniversalBle.requestMtu(deviceId, expectedMtu);
Future<int> requestMtu(int expectedMtu, {String? queueId}) =>
UniversalBle.requestMtu(deviceId, expectedMtu, queueId: queueId);

/// Check if a device is paired.
///
/// For `Apple` and `Web`, you have to pass a "pairingCommand" with an encrypted read or write characteristic.
/// Returns true/false if it manages to execute the command.
/// Returns null when no `pairingCommand` is passed.
/// Note that it will trigger pairing if the device is not already paired.
Future<bool?> isPaired({BleCommand? pairingCommand, Duration? timeout}) {
Future<bool?> isPaired({
BleCommand? pairingCommand,
Duration? timeout,
String? queueId,
}) {
return UniversalBle.isPaired(
deviceId,
pairingCommand: pairingCommand,
timeout: timeout,
queueId: queueId,
);
}

Expand All @@ -62,31 +68,38 @@ extension BleDeviceExtension on BleDevice {
///
/// On `Web/Windows` and `Web/Linux`, it does not work for devices that use `ConfirmOnly` pairing.
/// Can throw `PairingException`, `ConnectionException` or `PlatformException`.
Future<void> pair({BleCommand? pairingCommand, Duration? timeout}) {
Future<void> pair({
BleCommand? pairingCommand,
Duration? timeout,
String? queueId,
}) {
return UniversalBle.pair(
deviceId,
pairingCommand: pairingCommand,
timeout: timeout,
queueId: queueId,
);
}

/// Unpair a device.
///
/// It might throw an error if device is not paired.
Future<void> unpair({Duration? timeout}) =>
UniversalBle.unpair(deviceId, timeout: timeout);
Future<void> unpair({Duration? timeout, String? queueId}) =>
UniversalBle.unpair(deviceId, timeout: timeout, queueId: queueId);

/// Discovers the services offered by the device.
///
/// Returns cached services if already discovered after connection.
Future<List<BleService>> discoverServices({
Duration? timeout,
bool withDescriptors = false,
String? queueId,
}) async {
List<BleService> servicesCache = await UniversalBle.discoverServices(
deviceId,
withDescriptors: withDescriptors,
timeout: timeout,
queueId: queueId,
);
CacheHandler.instance.saveServices(deviceId, servicesCache);
return servicesCache;
Expand All @@ -101,13 +114,17 @@ extension BleDeviceExtension on BleDevice {
String service, {
bool preferCached = true,
Duration? timeout,
String? queueId,
}) async {
List<BleService> discoveredServices = [];
if (preferCached) {
discoveredServices = CacheHandler.instance.getServices(deviceId) ?? [];
}
if (discoveredServices.isEmpty) {
discoveredServices = await discoverServices(timeout: timeout);
discoveredServices = await discoverServices(
timeout: timeout,
queueId: queueId,
);
}

if (discoveredServices.isEmpty) {
Expand Down Expand Up @@ -137,11 +154,13 @@ extension BleDeviceExtension on BleDevice {
required String service,
bool preferCached = true,
Duration? timeout,
String? queueId,
}) async {
BleService bluetoothService = await getService(
service,
preferCached: preferCached,
timeout: timeout,
queueId: queueId,
);
return bluetoothService.getCharacteristic(characteristic);
}
Expand Down
Loading
Loading