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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 1.3.0
* Add `requestConnectionPriority` to allow tuning BLE connection intervals on Android

## 1.2.0
* Add `autoConnect` parameter to `connect()` method for automatic reconnection support on Android and iOS/macOS
* Add `serviceData` in `BleDevice`
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE
| enable/disable Bluetooth | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ❌ |
| onAvailabilityChange | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| requestMtu | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
| requestConnectionPriority | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ |
| readRssi | ✔️ | ✔️ | ✔️ | ❌ | 🚧 | ❌ |
| requestPermissions | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |

Expand Down Expand Up @@ -470,6 +471,21 @@ When developing cross-platform BLE applications and devices:
* Take advantage of higher MTUs when available, without depending on them


### Requesting Connection Priority

On Android, you can request a connection parameter update to tune the BLE connection interval. This can yield a 3–7× throughput improvement for data-intensive transfers.

```dart
// Before starting high-throughput data transfer:
await UniversalBle.requestConnectionPriority(
deviceId,
BleConnectionPriority.highPerformance,
);
```

> **Note:** Only supported on Android. On all other platforms this throws `UniversalBleException` with code `notSupported`.
> Call this after connecting and after `requestMtu()`, before beginning data transfer.

### Reading RSSI

Read the signal strength (RSSI) of a connected device.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@ interface UniversalBlePlatformChannel {
fun getSystemDevices(withServices: List<String>, callback: (Result<List<UniversalBleScanResult>>) -> Unit)
fun getConnectionState(deviceId: String): Long
fun readRssi(deviceId: String, callback: (Result<Long>) -> Unit)
fun requestConnectionPriority(deviceId: String, priority: Long, callback: (Result<Unit>) -> Unit)
fun setLogLevel(logLevel: UniversalBleLogLevel)

companion object {
Expand Down Expand Up @@ -1057,6 +1058,26 @@ interface UniversalBlePlatformChannel {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestConnectionPriority$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val priorityArg = args[1] as Long
api.requestConnectionPriority(deviceIdArg, priorityArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(UniversalBlePigeonUtils.wrapError(error))
} else {
reply.reply(UniversalBlePigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$separatedMessageChannelSuffix", codec)
if (api != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ enum class BleOutputProperty(val value: Long) {
WithoutResponse(1);
}

enum class BleConnectionPriority(val value: Long) {
Balanced(0),
HighPerformance(1),
LowPower(2);
}

enum class CharacteristicProperty(val value: Long) {
Broadcast(0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,55 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
}
}

override fun requestConnectionPriority(
deviceId: String,
priority: Long,
callback: (Result<Unit>) -> Unit,
) {
try {
val gatt = deviceId.toBluetoothGatt()
val priorityEnum = BleConnectionPriority.entries.firstOrNull { it.value == priority }
?: return callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.ILLEGAL_ARGUMENT,
"Unknown priority: $priority"
)
)
)
val androidPriority = when (priorityEnum) {
BleConnectionPriority.Balanced -> BluetoothGatt.CONNECTION_PRIORITY_BALANCED
BleConnectionPriority.HighPerformance -> BluetoothGatt.CONNECTION_PRIORITY_HIGH
BleConnectionPriority.LowPower -> BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER
}
val success = gatt.requestConnectionPriority(androidPriority)
if (success) {
Comment thread
fotiDim marked this conversation as resolved.
callback(Result.success(Unit))
} else {
callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.FAILED,
"requestConnectionPriority returned false",
),
),
)
}
} catch (e: FlutterError) {
callback(Result.failure(e))
Comment thread
fotiDim marked this conversation as resolved.
} catch (e: Exception) {
callback(
Result.failure(
createFlutterError(
UniversalBleErrorCode.FAILED,
"requestConnectionPriority failed",
e.toString()
)
)
)
}
}

override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
val deviceId = gatt?.device?.address ?: return
mtuResultFutureList.removeAll {
Expand Down
19 changes: 19 additions & 0 deletions darwin/Classes/UniversalBle.g.swift
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@ protocol UniversalBlePlatformChannel {
func getSystemDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void)
func getConnectionState(deviceId: String) throws -> Int64
func readRssi(deviceId: String, completion: @escaping (Result<Int64, Error>) -> Void)
func requestConnectionPriority(deviceId: String, priority: Int64, completion: @escaping (Result<Void, Error>) -> Void)
func setLogLevel(logLevel: UniversalBleLogLevel) throws
}

Expand Down Expand Up @@ -998,6 +999,24 @@ class UniversalBlePlatformChannelSetup {
} else {
readRssiChannel.setMessageHandler(nil)
}
let requestConnectionPriorityChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestConnectionPriority\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
requestConnectionPriorityChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
let priorityArg = args[1] as! Int64
api.requestConnectionPriority(deviceId: deviceIdArg, priority: priorityArg) { result in
switch result {
case .success:
reply(wrapResult(nil))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
requestConnectionPriorityChannel.setMessageHandler(nil)
}
let setLogLevelChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
setLogLevelChannel.setMessageHandler { message, reply in
Expand Down
8 changes: 8 additions & 0 deletions darwin/Classes/UniversalBlePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,14 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
completion(Result.success(mtuResult))
}

func requestConnectionPriority(
deviceId _: String,
priority _: Int64,
completion: @escaping (Result<Void, Error>) -> Void
) {
completion(.failure(createFlutterError(code: .notSupported, message: "requestConnectionPriority is not supported on Apple platforms")))
}

func readRssi(deviceId: String, completion: @escaping (Result<Int64, Error>) -> Void) {
UniversalBleLogger.shared.logDebug("READ_RSSI -> \(deviceId)")
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
Expand Down
6 changes: 6 additions & 0 deletions example/lib/data/mock_universal_ble.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ class MockUniversalBle extends UniversalBlePlatform {
return 512;
}

@override
Future<void> requestConnectionPriority(
String deviceId,
BleConnectionPriority priority,
) async {}

@override
Future<void> setNotifiable(String deviceId, String service,
String characteristic, BleInputProperty bleInputProperty) async {}
Expand Down
3 changes: 3 additions & 0 deletions lib/src/models/ble_capabilities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class BleCapabilities {
static bool supportsConnectedDevicesApi = !_Platform.isWeb;

static bool supportsRequestMtuApi = !_Platform.isWeb;

static bool supportsConnectionPriorityApi =
!_Platform.isWeb && defaultTargetPlatform == TargetPlatform.android;
}

class _Platform {
Expand Down
13 changes: 13 additions & 0 deletions lib/src/models/ble_connection_priority.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// Connection priority hint passed to [UniversalBle.requestConnectionPriority].
///
/// Maps to Android `BluetoothGatt.CONNECTION_PRIORITY_*` constants.
enum BleConnectionPriority {
/// Default OS-managed interval (~30-50 ms). Android constant: 0.
balanced,

/// Low-latency interval (~7.5-15 ms), higher power draw. Android constant: 1.
highPerformance,

/// Power-optimised interval (~100-125 ms). Android constant: 2.
lowPower,
}
1 change: 1 addition & 0 deletions lib/src/models/model_exports.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export 'package:universal_ble/src/models/ble_connection_state.dart';
export 'package:universal_ble/src/models/ble_device.dart';
export 'package:universal_ble/src/models/ble_command.dart';
export 'package:universal_ble/src/models/ble_capabilities.dart';
export 'package:universal_ble/src/models/ble_connection_priority.dart';
24 changes: 24 additions & 0 deletions lib/src/universal_ble.dart
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,30 @@ class UniversalBle {
);
}

/// Requests a connection parameter update for [deviceId].
///
/// [priority] controls the BLE connection interval:
/// - [BleConnectionPriority.balanced] - default OS behaviour (~30-50 ms interval).
/// - [BleConnectionPriority.highPerformance] - low latency, higher power (~7.5-15 ms interval).
/// - [BleConnectionPriority.lowPower] - power-optimised (~100-125 ms interval).
///
/// Only supported on Android. On all other platforms this throws
/// [UniversalBleException] with code [UniversalBleErrorCode.notSupported].
///
/// Should be called after a successful connection and MTU negotiation, before
/// beginning high-throughput data transfer.
static Future<void> requestConnectionPriority(
String deviceId,
BleConnectionPriority priority, {
Duration? timeout,
}) async {
return await _bleCommandQueue.queueCommand(
() => _platform.requestConnectionPriority(deviceId, priority),
timeout: timeout,
deviceId: deviceId,
);
}
Comment thread
fotiDim marked this conversation as resolved.

/// Read the RSSI value of a connected device.
///
/// Returns the current RSSI value in dBm. This value indicates the signal strength
Expand Down
11 changes: 11 additions & 0 deletions lib/src/universal_ble_linux/universal_ble_linux.dart
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,17 @@ class UniversalBleLinux extends UniversalBlePlatform {
);
}

@override
Future<void> requestConnectionPriority(
String deviceId,
BleConnectionPriority priority,
) {
throw UniversalBleException(
code: UniversalBleErrorCode.notSupported,
message: "requestConnectionPriority is not supported on Linux platform",
);
}

@override
Future<int> readRssi(String deviceId) async {
throw UniversalBleException(
Expand Down
24 changes: 24 additions & 0 deletions lib/src/universal_ble_pigeon/universal_ble.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,30 @@ class UniversalBlePlatformChannel {
}
}

Future<void> requestConnectionPriority(String deviceId, int priority) async {
final pigeonVar_channelName =
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestConnectionPriority$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[deviceId, priority]);
final pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}

Future<void> setLogLevel(UniversalBleLogLevel logLevel) async {
final pigeonVar_channelName =
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setLogLevel$pigeonVar_messageChannelSuffix';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
Future<int> readRssi(String deviceId) =>
_executeWithErrorHandling(() => _channel.readRssi(deviceId));

@override
Future<void> requestConnectionPriority(
String deviceId,
BleConnectionPriority priority,
) => _executeWithErrorHandling(
() => _channel.requestConnectionPriority(deviceId, priority.index),
);

@override
Future<bool> isPaired(String deviceId) =>
_executeWithErrorHandling(() => _channel.isPaired(deviceId));
Expand Down
5 changes: 5 additions & 0 deletions lib/src/universal_ble_platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ abstract class UniversalBlePlatform {

Future<int> readRssi(String deviceId);

Future<void> requestConnectionPriority(
String deviceId,
BleConnectionPriority priority,
);

Future<bool> isPaired(String deviceId);

Future<bool> pair(String deviceId);
Expand Down
12 changes: 12 additions & 0 deletions lib/src/universal_ble_web/universal_ble_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,18 @@ class UniversalBleWeb extends UniversalBlePlatform {
);
}

/// `Unimplemented`
@override
Future<void> requestConnectionPriority(
String deviceId,
BleConnectionPriority priority,
) {
throw UniversalBleException(
code: UniversalBleErrorCode.notSupported,
message: "requestConnectionPriority is not supported on Web platform",
);
}

/// `Unimplemented`
@override
Future<int> readRssi(String deviceId) {
Expand Down
3 changes: 3 additions & 0 deletions pigeon/universal_ble.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ abstract class UniversalBlePlatformChannel {
@async
int readRssi(String deviceId);

@async
void requestConnectionPriority(String deviceId, int priority);

void setLogLevel(UniversalBleLogLevel logLevel);
}

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: universal_ble
description: A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter
version: 1.2.0
version: 1.3.0
homepage: https://navideck.com
repository: https://github.com/Navideck/universal_ble
issue_tracker: https://github.com/Navideck/universal_ble/issues
Expand Down
8 changes: 8 additions & 0 deletions test/universal_ble_test_mock.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ abstract class UniversalBlePlatformMock extends UniversalBlePlatform {
throw UnimplementedError();
}

@override
Future<void> requestConnectionPriority(
String deviceId,
BleConnectionPriority priority,
) {
throw UnimplementedError();
}

@override
Future<void> setNotifiable(String deviceId, String service,
String characteristic, BleInputProperty bleInputProperty) {
Expand Down
Loading
Loading