Add connection properties in BleDevice (#50)

* Rename to BleDevice

* Add isConnected property

* Make isConnected true

* Add isConnected api

* Implement Windows

* Merge add-isConnected-api

* Rename getConnectedDevices to getSystemDevices

* Add code level Doc for isConnected

* Add isPaired

* Revert isPaired

* Add isSystemDevice property in BleDevice

* Return enum instead of bool

---------

Co-authored-by: Rohit Sangwan <rohitsangwan647@gmail.com>
This commit is contained in:
Foti Dim
2024-06-13 16:14:54 +02:00
committed by GitHub
parent 7d6714e53a
commit 6361574b77
28 changed files with 346 additions and 179 deletions
+6 -2
View File
@@ -1,8 +1,12 @@
## 0.9.12
* Add .perDevice queue
* Improve code level documentation
* BREAKING CHANGE: Rename ScanResult to BleDevice
* Add `connectionState` property to BleDevice
* Add `isSystemDevice` property to BleDevice
* Add `.perDevice` queue
* Support "ProvidePin" pairing on Windows 10/11
* Get RRSI updates on Apple platforms
* Improve code level documentation
* Improve enum parsing performance
## 0.9.11
* Add device name prefix filtering
+13 -8
View File
@@ -23,7 +23,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE
| :------------------- | :-----: | :-: | :---: | :-----: | :----------: | :-: |
| startScan/stopScan | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| connect/disconnect | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| getConnectedDevices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
| getSystemDevices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
| discoverServices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| readValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| writeValue | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
@@ -53,8 +53,8 @@ import 'package:universal_ble/universal_ble.dart';
```dart
// Set a scan result handler
UniversalBle.onScanResult = (scanResult) {
// e.g. Use scan result to connect
UniversalBle.onScanResult = (bleDevice) {
// e.g. Use BleDevice ID to connect
}
// Perform a scan
@@ -73,6 +73,7 @@ UniversalBle.stopScan();
```
Before initiating a scan, ensure that Bluetooth is available:
```dart
AvailabilityState state = await UniversalBle.getBluetoothAvailabilityState()
// Start scan only if Bluetooth is powered on
@@ -93,13 +94,16 @@ See the [Bluetooth Availability](#bluetooth-availability) section for more.
#### Connected Devices
Already connected devices, either through previous sessions or connected through system settings, won't show up as scan results.
You can list those devices using `getConnectedDevices()`. You still need to explicitly connect before using them.
You can list those devices using `getSystemDevices()`. You still need to explicitly connect before using them.
```dart
// Get connected devices
// You can set `withServices` to narrow down the results
await UniversalBle.getConnectedDevices(withServices: []);
List<BleDevice> devices = await UniversalBle.getSystemDevices(withServices: []);
```
For each connected device the `isConnected` property will be `true`.
#### Scan Filter
You can optionally set filters when scanning.
@@ -119,6 +123,7 @@ Use the `withManufacturerData` parameter to filter devices by manufacturer data.
```dart
List<ManufacturerDataFilter> withManufacturerData;
```
##### With namePrefix
Use the `withNamePrefix` parameter to filter devices by names (case sensitive). When you pass a list of names, the scan results will only include devices that have this name or start with the provided parameter.
@@ -130,8 +135,8 @@ List<String> withNamePrefix;
### Connecting
```dart
// Connect to a device using the `deviceId` of the scanResult received from `UniversalBle.onScanResult`
String deviceId = scanResult.deviceId;
// Connect to a device using the `deviceId` of the BleDevice received from `UniversalBle.onScanResult`
String deviceId = bleDevice.deviceId;
UniversalBle.connect(deviceId);
// Disconnect from a device
@@ -215,7 +220,7 @@ By default, all commands are executed in a global queue (`QueueType.global`), wi
If you want to parallelize commands between multiple devices, you can set:
```dart
// Create a separate queue for each device.
// Create a separate queue for each device.
UniversalBle.queueType = QueueType.perDevice;
```
@@ -257,7 +257,8 @@ interface UniversalBlePlatformChannel {
fun isPaired(deviceId: String, callback: (Result<Boolean>) -> Unit)
fun pair(deviceId: String)
fun unPair(deviceId: String)
fun getConnectedDevices(withServices: List<String>, callback: (Result<List<UniversalBleScanResult>>) -> Unit)
fun getSystemDevices(withServices: List<String>, callback: (Result<List<UniversalBleScanResult>>) -> Unit)
fun isConnected(deviceId: String): Boolean
companion object {
/** The codec used by UniversalBlePlatformChannel. */
@@ -538,12 +539,12 @@ interface UniversalBlePlatformChannel {
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectedDevices$separatedMessageChannelSuffix", codec)
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val withServicesArg = args[0] as List<String>
api.getConnectedDevices(withServicesArg) { result: Result<List<UniversalBleScanResult>> ->
api.getSystemDevices(withServicesArg) { result: Result<List<UniversalBleScanResult>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(wrapError(error))
@@ -557,6 +558,23 @@ interface UniversalBlePlatformChannel {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isConnected$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
val wrapped: List<Any?> = try {
listOf<Any?>(api.isConnected(deviceIdArg))
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -25,7 +25,7 @@ import java.util.UUID
private const val TAG = "UniversalBlePlugin"
val knownGatts = mutableListOf<BluetoothGatt>()
val ccdCharacteristic = "00002902-0000-1000-8000-00805f9b34fb"
const val ccdCharacteristic = "00002902-0000-1000-8000-00805f9b34fb"
enum class BleConnectionState(val value: Long) {
Connected(0),
@@ -180,6 +180,10 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
cleanConnection(deviceId.toBluetoothGatt())
}
override fun isConnected(deviceId: String): Boolean {
return devicesStateMap[deviceId] == BluetoothGatt.STATE_CONNECTED
}
override fun discoverServices(
deviceId: String,
callback: (Result<List<UniversalBleService>>) -> Unit,
@@ -498,7 +502,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
}
}
override fun getConnectedDevices(
override fun getSystemDevices(
withServices: List<String>,
callback: (Result<List<UniversalBleScanResult>>) -> Unit,
) {
+21 -5
View File
@@ -258,7 +258,8 @@ protocol UniversalBlePlatformChannel {
func isPaired(deviceId: String, completion: @escaping (Result<Bool, Error>) -> Void)
func pair(deviceId: String) throws
func unPair(deviceId: String) throws
func getConnectedDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void)
func getSystemDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void)
func isConnected(deviceId: String) throws -> Bool
}
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@@ -498,12 +499,12 @@ class UniversalBlePlatformChannelSetup {
} else {
unPairChannel.setMessageHandler(nil)
}
let getConnectedDevicesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectedDevices\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
let getSystemDevicesChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
getConnectedDevicesChannel.setMessageHandler { message, reply in
getSystemDevicesChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let withServicesArg = args[0] as! [String]
api.getConnectedDevices(withServices: withServicesArg) { result in
api.getSystemDevices(withServices: withServicesArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
@@ -513,7 +514,22 @@ class UniversalBlePlatformChannelSetup {
}
}
} else {
getConnectedDevicesChannel.setMessageHandler(nil)
getSystemDevicesChannel.setMessageHandler(nil)
}
let isConnectedChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isConnected\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
isConnectedChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
do {
let result = try api.isConnected(deviceId: deviceIdArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
isConnectedChannel.setMessageHandler(nil)
}
}
}
+8 -1
View File
@@ -95,6 +95,13 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
cleanUpConnection(deviceId: deviceId)
}
func isConnected(deviceId: String) -> Bool {
guard let peripheral = discoveredPeripherals[deviceId] else {
return false
}
return peripheral.state == CBPeripheralState.connected
}
func cleanUpConnection(deviceId: String) {
characteristicReadFutures.removeAll { future in
if future.deviceId == deviceId {
@@ -254,7 +261,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
throw FlutterError(code: "NotSupported", message: nil, details: nil)
}
func getConnectedDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void) {
func getSystemDevices(withServices: [String], completion: @escaping (Result<[UniversalBleScanResult], Error>) -> Void) {
var filterCBUUID = withServices.map { CBUUID(string: $0) }
// We can't keep this filter empty, so adding a default filter
if filterCBUUID.isEmpty { filterCBUUID.append(CBUUID(string: "1800")) }
+9 -8
View File
@@ -5,7 +5,7 @@ import 'package:universal_ble/universal_ble.dart';
/// Mock implementation of [UniversalBlePlatform] for testing
class MockUniversalBle extends UniversalBlePlatform {
final _mockBleScanResult = BleScanResult(
final _mockBleDevice = BleDevice(
name: 'MockDevice',
deviceId: 'MockDeviceId',
rssi: 50,
@@ -23,11 +23,8 @@ class MockUniversalBle extends UniversalBlePlatform {
]);
@override
Future<void> startScan({
ScanFilter? scanFilter,
}) async {
onScanResult?.call(_mockBleScanResult);
}
Future<void> startScan({ScanFilter? scanFilter}) async =>
onScanResult?.call(_mockBleDevice);
@override
Future<void> stopScan() async {}
@@ -59,8 +56,7 @@ class MockUniversalBle extends UniversalBlePlatform {
}
@override
Future<List<BleScanResult>> getConnectedDevices(
List<String>? withServices) async {
Future<List<BleDevice>> getSystemDevices(List<String>? withServices) async {
return [];
}
@@ -107,4 +103,9 @@ class MockUniversalBle extends UniversalBlePlatform {
Future<void> unPair(String deviceId) async {
onPairingStateChange?.call(deviceId, false, null);
}
@override
Future<bool> isConnected(String deviceId) {
throw UnimplementedError();
}
}
+22 -21
View File
@@ -20,7 +20,7 @@ class MyApp extends StatefulWidget {
}
class _MyAppState extends State<MyApp> {
final _scanResults = <BleScanResult>[];
final _bleDevices = <BleDevice>[];
bool _isScanning = false;
QueueType _queueType = QueueType.global;
@@ -55,16 +55,16 @@ class _MyAppState extends State<MyApp> {
};
UniversalBle.onScanResult = (result) {
// debugPrint("ScanResult: ${result.name} ${result.services}");
// debugPrint("BleDevice: ${result.name} ${result.services}");
// debugPrint("${result.name} ${result.manufacturerData}");
int index = _scanResults.indexWhere((e) => e.deviceId == result.deviceId);
int index = _bleDevices.indexWhere((e) => e.deviceId == result.deviceId);
if (index == -1) {
_scanResults.add(result);
_bleDevices.add(result);
} else {
if (result.name == null && _scanResults[index].name != null) {
result.name = _scanResults[index].name;
if (result.name == null && _bleDevices[index].name != null) {
result.name = _bleDevices[index].name;
}
_scanResults[index] = result;
_bleDevices[index] = result;
}
setState(() {});
};
@@ -119,7 +119,7 @@ class _MyAppState extends State<MyApp> {
text: 'Start Scan',
onPressed: () async {
setState(() {
_scanResults.clear();
_bleDevices.clear();
_isScanning = true;
});
try {
@@ -171,7 +171,8 @@ class _MyAppState extends State<MyApp> {
PlatformButton(
text: 'Connected Devices',
onPressed: () async {
var devices = await UniversalBle.getConnectedDevices(
List<BleDevice> devices =
await UniversalBle.getSystemDevices(
withServices: _services,
);
if (devices.isEmpty) {
@@ -182,8 +183,8 @@ class _MyAppState extends State<MyApp> {
);
}
setState(() {
_scanResults.clear();
_scanResults.addAll(devices);
_bleDevices.clear();
_bleDevices.addAll(devices);
});
},
),
@@ -200,12 +201,12 @@ class _MyAppState extends State<MyApp> {
});
},
),
if (_scanResults.isNotEmpty)
if (_bleDevices.isNotEmpty)
PlatformButton(
text: 'Clear List',
onPressed: () {
setState(() {
_scanResults.clear();
_bleDevices.clear();
});
},
),
@@ -225,25 +226,25 @@ class _MyAppState extends State<MyApp> {
),
const Divider(color: Colors.blue),
Expanded(
child: _isScanning && _scanResults.isEmpty
child: _isScanning && _bleDevices.isEmpty
? const Center(child: CircularProgressIndicator.adaptive())
: !_isScanning && _scanResults.isEmpty
: !_isScanning && _bleDevices.isEmpty
? const ScannedDevicesPlaceholderWidget()
: ListView.separated(
itemCount: _scanResults.length,
itemCount: _bleDevices.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
BleScanResult scanResult =
_scanResults[_scanResults.length - index - 1];
BleDevice device =
_bleDevices[_bleDevices.length - index - 1];
return ScannedItemWidget(
scanResult: scanResult,
bleDevice: device,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PeripheralDetailPage(
scanResult.deviceId,
scanResult.name ?? "Unknown Peripheral",
device.deviceId,
device.name ?? "Unknown Peripheral",
),
));
UniversalBle.stopScan();
@@ -4,14 +4,14 @@ import 'package:universal_ble/universal_ble.dart';
import 'package:universal_ble_example/data/capabilities.dart';
class ScannedItemWidget extends StatelessWidget {
final BleScanResult scanResult;
final BleDevice bleDevice;
final VoidCallback? onTap;
const ScannedItemWidget({super.key, required this.scanResult, this.onTap});
const ScannedItemWidget({super.key, required this.bleDevice, this.onTap});
@override
Widget build(BuildContext context) {
String? name = scanResult.name;
Uint8List? rawManufacturerData = scanResult.manufacturerData;
String? name = bleDevice.name;
Uint8List? rawManufacturerData = bleDevice.manufacturerData;
ManufacturerData? manufacturerData;
if (rawManufacturerData != null && rawManufacturerData.isNotEmpty) {
manufacturerData = ManufacturerData.fromData(rawManufacturerData);
@@ -22,12 +22,12 @@ class ScannedItemWidget extends StatelessWidget {
child: Card(
child: ListTile(
title: Text(
'$name (${scanResult.rssi})',
'$name (${bleDevice.rssi})',
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(scanResult.deviceId),
Text(bleDevice.deviceId),
Visibility(
visible: manufacturerData != null,
child: Text(
@@ -36,18 +36,15 @@ class ScannedItemWidget extends StatelessWidget {
: 'ManufacturerCompanyId: ${manufacturerData?.companyIdRadix16}',
),
),
Visibility(
visible: scanResult.isPaired != null,
child: scanResult.isPaired == true
? const Text(
"Paired",
style: TextStyle(color: Colors.green),
)
: const Text(
"Not Paired",
style: TextStyle(color: Colors.red),
),
),
bleDevice.isPaired == true
? const Text(
"Paired",
style: TextStyle(color: Colors.green),
)
: const Text(
"Not Paired",
style: TextStyle(color: Colors.red),
),
],
),
trailing: const Icon(Icons.arrow_forward_ios),
@@ -344,6 +344,17 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
enabled: isConnected,
text: 'Discover Services',
),
PlatformButton(
onPressed: () async {
_addLog(
'IsConnected',
await UniversalBle.isConnected(
widget.deviceId,
),
);
},
text: 'IsConnected',
),
if (Capabilities.supportsRequestMtuApi)
PlatformButton(
enabled: isConnected,
@@ -405,7 +416,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
if (Capabilities.supportsPairingApi)
PlatformButton(
onPressed: () async {
bool isPaired = await UniversalBle.isPaired(
bool? isPaired = await UniversalBle.isPaired(
widget.deviceId);
_addLog('IsPaired', isPaired);
},
+8 -10
View File
@@ -1,14 +1,12 @@
enum AvailabilityState {
unknown(0),
resetting(1),
unsupported(2),
unauthorized(3),
poweredOff(4),
poweredOn(5);
unknown,
resetting,
unsupported,
unauthorized,
poweredOff,
poweredOn;
final int value;
const AvailabilityState(this.value);
const AvailabilityState();
factory AvailabilityState.parse(int value) =>
AvailabilityState.values.firstWhere((element) => element.value == value);
factory AvailabilityState.parse(int index) => AvailabilityState.values[index];
}
+5 -6
View File
@@ -1,10 +1,9 @@
enum BleConnectionState {
connected(0),
disconnected(1);
connected,
disconnected;
final int value;
const BleConnectionState(this.value);
const BleConnectionState();
factory BleConnectionState.parse(int value) =>
BleConnectionState.values.firstWhere((element) => element.value == value);
factory BleConnectionState.parse(int index) =>
BleConnectionState.values[index];
}
@@ -1,22 +1,28 @@
import 'dart:typed_data';
class BleScanResult {
import 'package:universal_ble/universal_ble.dart';
class BleDevice {
String deviceId;
String? name;
int? rssi;
bool? isPaired;
List<String> services;
bool? isSystemDevice;
Uint8List? manufacturerDataHead;
Uint8List? manufacturerData;
int? rssi;
List<String> services;
BleScanResult({
required this.name,
Future<BleConnectionState> get connectionState async => await UniversalBle.isConnected(deviceId) ? BleConnectionState.connected : BleConnectionState.disconnected;
BleDevice({
required this.deviceId,
required this.name,
this.rssi,
this.isPaired,
this.services = const [],
this.isSystemDevice,
Uint8List? manufacturerData,
Uint8List? manufacturerDataHead,
this.services = const [],
}) {
this.manufacturerDataHead = manufacturerDataHead ?? Uint8List.fromList([]);
this.manufacturerData = manufacturerData ?? manufacturerDataHead;
@@ -24,7 +30,7 @@ class BleScanResult {
}
/// Represents the manufacturer data of a BLE device.
/// Use [BleScanResult.manufacturerData] with [ManufacturerData.fromData] to create an instance of this class.
/// Use [BleDevice.manufacturerData] with [ManufacturerData.fromData] to create an instance of this class.
class ManufacturerData {
final int? companyId;
final Uint8List? data;
+9 -13
View File
@@ -1,22 +1,18 @@
enum BleInputProperty {
disabled(0),
notification(1),
indication(2);
disabled,
notification,
indication;
final int value;
const BleInputProperty(this.value);
const BleInputProperty();
factory BleInputProperty.parse(int value) =>
BleInputProperty.values.firstWhere((element) => element.value == value);
factory BleInputProperty.parse(int index) => BleInputProperty.values[index];
}
enum BleOutputProperty {
withResponse(0),
withoutResponse(1);
withResponse,
withoutResponse;
final int value;
const BleOutputProperty(this.value);
const BleOutputProperty();
factory BleOutputProperty.parse(int value) =>
BleOutputProperty.values.firstWhere((element) => element.value == value);
factory BleOutputProperty.parse(int index) => BleOutputProperty.values[index];
}
+11 -13
View File
@@ -11,19 +11,17 @@ class BleCharacteristic {
}
enum CharacteristicProperty {
broadcast(0),
read(1),
writeWithoutResponse(2),
write(3),
notify(4),
indicate(5),
authenticatedSignedWrites(6),
extendedProperties(7);
broadcast,
read,
writeWithoutResponse,
write,
notify,
indicate,
authenticatedSignedWrites,
extendedProperties;
final int value;
const CharacteristicProperty(this.value);
const CharacteristicProperty();
factory CharacteristicProperty.parse(int value) =>
CharacteristicProperty.values
.firstWhere((element) => element.value == value);
factory CharacteristicProperty.parse(int index) =>
CharacteristicProperty.values[index];
}
+1 -1
View File
@@ -5,6 +5,6 @@ export 'package:universal_ble/src/models/ble_property.dart';
export 'package:universal_ble/src/models/ble_service.dart';
export 'package:universal_ble/src/models/availability_state.dart';
export 'package:universal_ble/src/models/ble_connection_state.dart';
export 'package:universal_ble/src/models/ble_scan_result.dart';
export 'package:universal_ble/src/models/ble_device.dart';
+15 -6
View File
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:universal_ble/src/ble_command_queue.dart';
@@ -161,8 +162,9 @@ class UniversalBle {
}
/// Check if a device is paired
/// Pair commands are not supported on `Apple` and `Web`
static Future<bool> isPaired(String deviceId) async {
/// Returns null on `Apple` and `Web`
static Future<bool?> isPaired(String deviceId) async {
if (kIsWeb || Platform.isIOS || Platform.isMacOS) return null;
return await _bleCommandQueue.executeCommand(
() => _platform.isPaired(deviceId),
deviceId: deviceId,
@@ -192,11 +194,18 @@ class UniversalBle {
/// On `Apple`, [withServices] is required to get connected devices, else [1800] service will be used as default filter
/// On `Android`, `Linux` and `Windows`, if [withServices] is used, then internally all services will be discovered for each device first (either by connecting or by using cached services)
/// Not supported on `Web`
static Future<List<BleScanResult>> getConnectedDevices({
static Future<List<BleDevice>> getSystemDevices({
List<String>? withServices,
}) async {
return await _bleCommandQueue.executeCommand(
() => _platform.getConnectedDevices(withServices),
() => _platform.getSystemDevices(withServices),
);
}
/// Returns true if device is connected to the app
static Future<bool> isConnected(String deviceId) async {
return await _bleCommandQueue.executeCommand(
() => _platform.isConnected(deviceId),
);
}
@@ -224,8 +233,8 @@ class UniversalBle {
_bleCommandQueue.onQueueUpdate = onQueueUpdate;
/// Get scan results
static set onScanResult(OnScanResult? onScanResult) =>
_platform.onScanResult = onScanResult;
static set onScanResult(OnScanResult? bleDevice) =>
_platform.onScanResult = bleDevice;
/// Get connection state changes
static set onConnectionChanged(OnConnectionChanged? onConnectionChanged) =>
@@ -84,6 +84,15 @@ class UniversalBleLinux extends UniversalBlePlatform {
}
}
@override
Future<bool> isConnected(String deviceId) async {
BlueZDevice? device = _devices[deviceId] ??
_client.devices.cast<BlueZDevice?>().firstWhere(
(device) => device?.address == deviceId,
orElse: () => null);
return device?.connected ?? false;
}
@override
Future<void> connect(String deviceId, {Duration? connectionTimeout}) async {
final device = _findDeviceById(deviceId);
@@ -279,7 +288,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
}
@override
Future<List<BleScanResult>> getConnectedDevices(
Future<List<BleDevice>> getSystemDevices(
List<String>? withServices,
) async {
List<BlueZDevice> devices =
@@ -297,7 +306,9 @@ class UniversalBleLinux extends UniversalBlePlatform {
}
}).toList();
}
return devices.map((device) => device.toBleScanResult()).toList();
return devices
.map((device) => device.toBleDevice(isSystemDevice: true))
.toList();
}
AvailabilityState get _availabilityState {
@@ -400,7 +411,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
}
// Update scan results only if rssi is available
if (device.rssi != 0) updateScanResult(device.toBleScanResult());
if (device.rssi != 0) updateScanResult(device.toBleDevice());
// Setup Cache
_devices[device.address] = device;
@@ -415,7 +426,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
for (final property in properties) {
switch (property) {
case BluezProperty.rssi:
updateScanResult(device.toBleScanResult());
updateScanResult(device.toBleDevice());
break;
case BluezProperty.connected:
onConnectionChanged?.call(
@@ -426,7 +437,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
);
break;
case BluezProperty.manufacturerData:
updateScanResult(device.toBleScanResult());
updateScanResult(device.toBleDevice());
break;
case BluezProperty.paired:
onPairingStateChange?.call(device.address, device.paired, null);
@@ -591,14 +602,17 @@ extension BlueZDeviceExtension on BlueZDevice {
}
}
BleScanResult toBleScanResult() {
return BleScanResult(
BleDevice toBleDevice({
bool? isSystemDevice,
}) {
return BleDevice(
name: alias,
deviceId: address,
isPaired: paired,
manufacturerData: manufacturerDataHead,
manufacturerDataHead: manufacturerDataHead,
rssi: rssi,
isSystemDevice: isSystemDevice,
services: uuids.map((e) => e.toString()).toList(),
);
}
@@ -581,8 +581,8 @@ class UniversalBlePlatformChannel {
}
}
Future<List<UniversalBleScanResult?>> getConnectedDevices(List<String?> withServices) async {
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectedDevices$__pigeon_messageChannelSuffix';
Future<List<UniversalBleScanResult?>> getSystemDevices(List<String?> withServices) async {
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
@@ -607,6 +607,33 @@ class UniversalBlePlatformChannel {
return (__pigeon_replyList[0] as List<Object?>?)!.cast<UniversalBleScanResult?>();
}
}
Future<bool> isConnected(String deviceId) async {
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isConnected$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[deviceId]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else if (__pigeon_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (__pigeon_replyList[0] as bool?)!;
}
}
}
class _UniversalBleCallbackChannelCodec extends StandardMessageCodec {
@@ -40,6 +40,9 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
@override
Future<void> stopScan() => _channel.stopScan();
@override
Future<bool> isConnected(String deviceId) => _channel.isConnected(deviceId);
@override
Future<void> connect(String deviceId, {Duration? connectionTimeout}) =>
_channel.connect(deviceId);
@@ -64,7 +67,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
deviceId,
service,
characteristic,
bleInputProperty.value,
bleInputProperty.index,
);
}
@@ -86,7 +89,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
service,
characteristic,
value,
bleOutputProperty.value,
bleOutputProperty.index,
);
}
@@ -104,32 +107,32 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
Future<void> unPair(String deviceId) => _channel.unPair(deviceId);
@override
Future<List<BleScanResult>> getConnectedDevices(
Future<List<BleDevice>> getSystemDevices(
List<String>? withServices,
) async {
var devices = await _channel.getConnectedDevices(withServices ?? []);
return List<BleScanResult>.from(devices
.map((e) => e?.toBleScanResult())
.where((e) => e != null)
.toList());
var devices = await _channel.getSystemDevices(withServices ?? []);
return List<BleDevice>.from(
devices
.map((e) => e?.toBleDevice(isSystemDevice: true))
.where((e) => e != null)
.toList(),
);
}
/// To set listeners
void _setupListeners() {
UniversalBleCallbackChannel.setUp(
_UniversalBleCallbackHandler(
scanResult: (BleScanResult scanResult) => updateScanResult(scanResult),
availabilityChange: (AvailabilityState state) =>
onAvailabilityChange?.call(state),
connectionChanged: (String deviceId, BleConnectionState state) =>
onConnectionChanged?.call(deviceId, state),
valueChanged:
(String deviceId, String characteristicId, Uint8List value) =>
onValueChanged?.call(deviceId, characteristicId, value),
pairStateChange: (String deviceId, bool isPaired, String? error) =>
onPairingStateChange?.call(deviceId, isPaired, error),
),
);
UniversalBleCallbackChannel.setUp(_UniversalBleCallbackHandler(
scanResult: (BleDevice bleDevice) => updateScanResult(bleDevice),
availabilityChange: (AvailabilityState state) =>
onAvailabilityChange?.call(state),
connectionChanged: (String deviceId, BleConnectionState state) =>
onConnectionChanged?.call(deviceId, state),
valueChanged:
(String deviceId, String characteristicId, Uint8List value) =>
onValueChanged?.call(deviceId, characteristicId, value),
pairStateChange: (String deviceId, bool isPaired, String? error) =>
onPairingStateChange?.call(deviceId, isPaired, error),
));
}
}
@@ -175,7 +178,7 @@ class _UniversalBleCallbackHandler extends UniversalBleCallbackChannel {
@override
void onScanResult(UniversalBleScanResult result) =>
scanResult(result.toBleScanResult());
scanResult(result.toBleDevice());
@override
void onValueChanged(
@@ -188,16 +191,19 @@ class _UniversalBleCallbackHandler extends UniversalBleCallbackChannel {
}
extension _UniversalBleScanResultExtension on UniversalBleScanResult {
BleScanResult toBleScanResult() {
BleDevice toBleDevice({
bool? isSystemDevice,
}) {
var mnfDataHead = manufacturerDataHead ?? Uint8List.fromList([]);
var mnfData = manufacturerData ?? mnfDataHead;
return BleScanResult(
return BleDevice(
name: name,
deviceId: deviceId,
isPaired: isPaired,
manufacturerData: mnfData,
manufacturerDataHead: mnfDataHead,
rssi: rssi,
isPaired: isPaired,
isSystemDevice: isSystemDevice,
services: services
?.where((e) => e != null)
.map((e) => UUID(e!).toString())
@@ -44,19 +44,21 @@ abstract class UniversalBlePlatform {
Future<void> unPair(String deviceId);
Future<List<BleScanResult>> getConnectedDevices(
Future<bool> isConnected(String deviceId);
Future<List<BleDevice>> getSystemDevices(
List<String>? withServices,
);
void updateScanResult(BleScanResult scanResult) {
void updateScanResult(BleDevice bleDevice) {
// Filter by name
ScanFilter? scanFilter = _scanFilter;
if (scanFilter != null && scanFilter.withNamePrefix.isNotEmpty) {
if (scanResult.name == null ||
if (bleDevice.name == null ||
!scanFilter.withNamePrefix
.any((e) => scanResult.name?.startsWith(e) == true)) return;
.any((e) => bleDevice.name?.startsWith(e) == true)) return;
}
onScanResult?.call(scanResult);
onScanResult?.call(bleDevice);
}
OnAvailabilityChange? onAvailabilityChange;
@@ -78,7 +80,7 @@ typedef OnConnectionChanged = void Function(
typedef OnValueChanged = void Function(
String deviceId, String characteristicId, Uint8List value);
typedef OnScanResult = void Function(BleScanResult scanResult);
typedef OnScanResult = void Function(BleDevice scanResult);
typedef OnAvailabilityChange = void Function(AvailabilityState state);
@@ -19,6 +19,13 @@ class UniversalBleWeb extends UniversalBlePlatform {
final Map<String, StreamSubscription> _connectedDeviceStreamList = {};
final Map<String, StreamSubscription> _characteristicStreamList = {};
@override
Future<bool> isConnected(String deviceId) async {
// TODO: Test this on Web (All platforms)
BluetoothDevice? device = _getDeviceById(deviceId);
return await device?.connected.first ?? false;
}
@override
Future<void> connect(
String deviceId, {
@@ -289,7 +296,7 @@ class UniversalBleWeb extends UniversalBlePlatform {
}
@override
Future<List<BleScanResult>> getConnectedDevices(
Future<List<BleDevice>> getSystemDevices(
List<String>? withServices,
) {
throw UnimplementedError();
@@ -351,12 +358,12 @@ class UniversalBleWeb extends UniversalBlePlatform {
}
extension _BluetoothDeviceExtension on BluetoothDevice {
BleScanResult toBleScanResult({
BleDevice toBleScanResult({
int? rssi,
UnmodifiableMapView<int, ByteData>? manufacturerDataMap,
List<String> services = const [],
}) {
return BleScanResult(
return BleDevice(
name: name,
deviceId: id,
manufacturerData: manufacturerDataMap?.toUint8List(),
+3 -1
View File
@@ -73,9 +73,11 @@ abstract class UniversalBlePlatformChannel {
void unPair(String deviceId);
@async
List<UniversalBleScanResult> getConnectedDevices(
List<UniversalBleScanResult> getSystemDevices(
List<String> withServices,
);
bool isConnected(String deviceId);
}
/// Native -> Flutter
+30 -2
View File
@@ -897,7 +897,7 @@ void UniversalBlePlatformChannel::SetUp(
}
}
{
BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectedDevices" + prepended_suffix, &GetCodec());
BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices" + prepended_suffix, &GetCodec());
if (api != nullptr) {
channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply<EncodableValue>& reply) {
try {
@@ -908,7 +908,7 @@ void UniversalBlePlatformChannel::SetUp(
return;
}
const auto& with_services_arg = std::get<EncodableList>(encodable_with_services_arg);
api->GetConnectedDevices(with_services_arg, [reply](ErrorOr<EncodableList>&& output) {
api->GetSystemDevices(with_services_arg, [reply](ErrorOr<EncodableList>&& output) {
if (output.has_error()) {
reply(WrapError(output.error()));
return;
@@ -925,6 +925,34 @@ void UniversalBlePlatformChannel::SetUp(
channel.SetMessageHandler(nullptr);
}
}
{
BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isConnected" + prepended_suffix, &GetCodec());
if (api != nullptr) {
channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply<EncodableValue>& reply) {
try {
const auto& args = std::get<EncodableList>(message);
const auto& encodable_device_id_arg = args.at(0);
if (encodable_device_id_arg.IsNull()) {
reply(WrapError("device_id_arg unexpectedly null."));
return;
}
const auto& device_id_arg = std::get<std::string>(encodable_device_id_arg);
ErrorOr<bool> output = api->IsConnected(device_id_arg);
if (output.has_error()) {
reply(WrapError(output.error()));
return;
}
EncodableList wrapped;
wrapped.push_back(EncodableValue(std::move(output).TakeValue()));
reply(EncodableValue(std::move(wrapped)));
} catch (const std::exception& exception) {
reply(WrapError(exception.what()));
}
});
} else {
channel.SetMessageHandler(nullptr);
}
}
}
EncodableValue UniversalBlePlatformChannel::WrapError(std::string_view error_message) {
+2 -1
View File
@@ -310,9 +310,10 @@ class UniversalBlePlatformChannel {
std::function<void(ErrorOr<bool> reply)> result) = 0;
virtual std::optional<FlutterError> Pair(const std::string& device_id) = 0;
virtual std::optional<FlutterError> UnPair(const std::string& device_id) = 0;
virtual void GetConnectedDevices(
virtual void GetSystemDevices(
const flutter::EncodableList& with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) = 0;
virtual ErrorOr<bool> IsConnected(const std::string& device_id) = 0;
// The codec used by UniversalBlePlatformChannel.
static const flutter::StandardMessageCodec& GetCodec();
+13 -4
View File
@@ -164,6 +164,15 @@ namespace universal_ble
}
};
ErrorOr<bool> UniversalBlePlugin::IsConnected(const std::string &device_id)
{
auto it = connectedDevices.find(_str_to_mac_address(device_id));
if (it == connectedDevices.end())
return false;
auto deviceAgent = *it->second;
return deviceAgent.device.ConnectionStatus() == BluetoothConnectionStatus::Connected;
}
std::optional<FlutterError> UniversalBlePlugin::Connect(const std::string &device_id)
{
ConnectAsync(_str_to_mac_address(device_id));
@@ -463,7 +472,7 @@ namespace universal_ble
}
};
void UniversalBlePlugin::GetConnectedDevices(
void UniversalBlePlugin::GetSystemDevices(
const flutter::EncodableList &with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result)
{
@@ -473,7 +482,7 @@ namespace universal_ble
auto serviceId = std::get<std::string>(item);
with_services_str.push_back(serviceId);
}
GetConnectedDevicesAsync(with_services_str, result);
GetSystemDevicesAsync(with_services_str, result);
}
/// Helper Methods
@@ -1088,7 +1097,7 @@ namespace universal_ble
}
}
winrt::fire_and_forget UniversalBlePlugin::GetConnectedDevicesAsync(
winrt::fire_and_forget UniversalBlePlugin::GetSystemDevicesAsync(
std::vector<std::string> with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result)
{
@@ -1143,7 +1152,7 @@ namespace universal_ble
}
catch (...)
{
std::cout << "Unknown error GetConnectedDevicesAsync" << std::endl;
std::cout << "Unknown error GetSystemDevicesAsyncAsync" << std::endl;
result(FlutterError("Unknown error"));
}
}
+3 -2
View File
@@ -118,7 +118,7 @@ namespace universal_ble
void GattCharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args);
AvailabilityState getAvailabilityStateFromRadio(RadioState radioState);
std::string parsePairingFailError(Enumeration::DevicePairingResult result);
winrt::fire_and_forget GetConnectedDevicesAsync(std::vector<std::string> with_services,
winrt::fire_and_forget GetSystemDevicesAsync(std::vector<std::string> with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result);
winrt::fire_and_forget IsPairedAsync(std::string device_id, std::function<void(ErrorOr<bool> reply)> result);
winrt::fire_and_forget WriteAsync(GattCharacteristic characteristic, GattWriteOption writeOption,
@@ -132,6 +132,7 @@ namespace universal_ble
// UniversalBlePlatformChannel implementation.
void GetBluetoothAvailabilityState(std::function<void(ErrorOr<int64_t> reply)> result) override;
void EnableBluetooth(std::function<void(ErrorOr<bool> reply)> result) override;
ErrorOr<bool> IsConnected(const std::string& device_id) override;
std::optional<FlutterError> StartScan(const UniversalScanFilter *filter) override;
std::optional<FlutterError> StopScan() override;
std::optional<FlutterError> Connect(const std::string &device_id) override;
@@ -166,7 +167,7 @@ namespace universal_ble
std::function<void(ErrorOr<bool> reply)> result) override;
std::optional<FlutterError> Pair(const std::string &device_id) override;
std::optional<FlutterError> UnPair(const std::string &device_id) override;
void GetConnectedDevices(
void GetSystemDevices(
const flutter::EncodableList &with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result);
};