Implement characteristic descriptor discovery (#196)

* Implement characteristic descriptor discovery

* Improve Darwin services discovery

* Minor mac fix

* Implement Windows support

* discover descriptors in DiscoverServicesAsync api on windows

* Add withDescriptors in discoverServices api

* Implement withDescriptors for Apple and Android

* Fix ai comment and format pigeon generated file

* Resolve Ai comment

* Capitalize API

* Handle empty services scenario

---------

Co-authored-by: Foti Dim <foti@navideck.com>
This commit is contained in:
Rohit Sangwan
2025-12-10 11:36:04 +05:30
committed by GitHub
parent 09bb22750a
commit 3116962e8c
25 changed files with 718 additions and 201 deletions
@@ -78,9 +78,11 @@ extension BleDeviceExtension on BleDevice {
/// Returns cached services if already discovered after connection.
Future<List<BleService>> discoverServices({
Duration? timeout,
bool withDescriptors = false,
}) async {
List<BleService> servicesCache = await UniversalBle.discoverServices(
deviceId,
withDescriptors: withDescriptors,
timeout: timeout,
);
CacheHandler.instance.saveServices(deviceId, servicesCache);
+20
View File
@@ -18,11 +18,13 @@ class BleService {
class BleCharacteristic {
String uuid;
List<CharacteristicProperty> properties;
List<BleDescriptor> descriptors;
({String deviceId, String serviceId})? metaData;
BleCharacteristic(
String uuid,
this.properties,
this.descriptors,
) : uuid = BleUuidParser.string(uuid);
BleCharacteristic.withMetaData({
@@ -30,6 +32,7 @@ class BleCharacteristic {
required String serviceId,
required String uuid,
required this.properties,
required this.descriptors,
}) : uuid = BleUuidParser.string(uuid),
metaData = (
deviceId: deviceId,
@@ -55,6 +58,23 @@ class BleCharacteristic {
int get hashCode => uuid.hashCode ^ properties.hashCode ^ metaData.hashCode;
}
class BleDescriptor {
String uuid;
BleDescriptor(String uuid) : uuid = BleUuidParser.string(uuid);
@override
String toString() => 'BleDescriptor{uuid: $uuid}';
@override
bool operator ==(Object other) {
if (other is! BleDescriptor) return false;
return other.uuid == uuid;
}
@override
int get hashCode => uuid.hashCode;
}
enum CharacteristicProperty {
broadcast,
read,
+3 -1
View File
@@ -183,12 +183,14 @@ class UniversalBle {
}
/// Discover services of a device.
/// Set [withDescriptors] to `true` to discover characteristics with descriptors.
static Future<List<BleService>> discoverServices(
String deviceId, {
bool withDescriptors = false,
Duration? timeout,
}) async {
return await _bleCommandQueue.queueCommand(
() => _platform.discoverServices(deviceId),
() => _platform.discoverServices(deviceId, withDescriptors),
timeout: timeout,
deviceId: deviceId,
);
@@ -160,7 +160,10 @@ class UniversalBleLinux extends UniversalBlePlatform {
}
@override
Future<List<BleService>> discoverServices(String deviceId) async {
Future<List<BleService>> discoverServices(
String deviceId,
bool withDescriptors,
) async {
final device = _findDeviceById(deviceId);
if (device.gattServices.isEmpty && !device.servicesResolved) {
await device.propertiesChanged.firstWhere((element) {
@@ -205,6 +208,11 @@ class UniversalBleLinux extends UniversalBlePlatform {
serviceId: serviceId,
uuid: e.uuid.toString(),
properties: properties,
descriptors: withDescriptors
? e.descriptors
.map((e) => BleDescriptor(e.uuid.toString()))
.toList()
: [],
);
}).toList();
services.add(
@@ -224,16 +224,20 @@ class UniversalBleCharacteristic {
UniversalBleCharacteristic({
required this.uuid,
required this.properties,
required this.descriptors,
});
String uuid;
List<int> properties;
List<UniversalBleDescriptor> descriptors;
List<Object?> _toList() {
return <Object?>[
uuid,
properties,
descriptors,
];
}
@@ -246,6 +250,8 @@ class UniversalBleCharacteristic {
return UniversalBleCharacteristic(
uuid: result[0]! as String,
properties: (result[1] as List<Object?>?)!.cast<int>(),
descriptors:
(result[2] as List<Object?>?)!.cast<UniversalBleDescriptor>(),
);
}
@@ -267,6 +273,47 @@ class UniversalBleCharacteristic {
int get hashCode => Object.hashAll(_toList());
}
class UniversalBleDescriptor {
UniversalBleDescriptor({
required this.uuid,
});
String uuid;
List<Object?> _toList() {
return <Object?>[
uuid,
];
}
Object encode() {
return _toList();
}
static UniversalBleDescriptor decode(Object result) {
result as List<Object?>;
return UniversalBleDescriptor(
uuid: result[0]! as String,
);
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (other is! UniversalBleDescriptor || other.runtimeType != runtimeType) {
return false;
}
if (identical(this, other)) {
return true;
}
return _deepEquals(encode(), other.encode());
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList());
}
/// Scan Filters
class UniversalScanFilter {
UniversalScanFilter({
@@ -438,15 +485,18 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is UniversalBleCharacteristic) {
buffer.putUint8(132);
writeValue(buffer, value.encode());
} else if (value is UniversalScanFilter) {
} else if (value is UniversalBleDescriptor) {
buffer.putUint8(133);
writeValue(buffer, value.encode());
} else if (value is UniversalManufacturerDataFilter) {
} else if (value is UniversalScanFilter) {
buffer.putUint8(134);
writeValue(buffer, value.encode());
} else if (value is UniversalManufacturerData) {
} else if (value is UniversalManufacturerDataFilter) {
buffer.putUint8(135);
writeValue(buffer, value.encode());
} else if (value is UniversalManufacturerData) {
buffer.putUint8(136);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@@ -465,10 +515,12 @@ class _PigeonCodec extends StandardMessageCodec {
case 132:
return UniversalBleCharacteristic.decode(readValue(buffer)!);
case 133:
return UniversalScanFilter.decode(readValue(buffer)!);
return UniversalBleDescriptor.decode(readValue(buffer)!);
case 134:
return UniversalManufacturerDataFilter.decode(readValue(buffer)!);
return UniversalScanFilter.decode(readValue(buffer)!);
case 135:
return UniversalManufacturerDataFilter.decode(readValue(buffer)!);
case 136:
return UniversalManufacturerData.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
@@ -768,7 +820,8 @@ class UniversalBlePlatformChannel {
}
}
Future<List<UniversalBleService>> discoverServices(String deviceId) async {
Future<List<UniversalBleService>> discoverServices(
String deviceId, bool withDescriptors) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
@@ -778,7 +831,7 @@ class UniversalBlePlatformChannel {
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[deviceId]);
pigeonVar_channel.send(<Object?>[deviceId, withDescriptors]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
@@ -77,10 +77,13 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
_executeWithErrorHandling(() => _channel.disconnect(deviceId));
@override
Future<List<BleService>> discoverServices(String deviceId) async {
Future<List<BleService>> discoverServices(
String deviceId,
bool withDescriptors,
) async {
List<UniversalBleService?> universalBleServices =
await _executeWithErrorHandling(
() => _channel.discoverServices(deviceId));
() => _channel.discoverServices(deviceId, withDescriptors));
return List<BleService>.from(universalBleServices
.where((e) => e != null)
.map((e) => e!.toBleService(deviceId))
@@ -210,6 +213,8 @@ extension _BleServiceExtension on UniversalBleService {
deviceId: deviceId,
serviceId: uuid,
uuid: characteristic.uuid,
descriptors: List<BleDescriptor>.from(
characteristic.descriptors.map((e) => BleDescriptor(e.uuid))),
properties: List<CharacteristicProperty>.from(
properties.map((e) => CharacteristicProperty.parse(e ?? 1)),
),
@@ -51,7 +51,8 @@ abstract class UniversalBlePlatform {
Future<void> disconnect(String deviceId);
Future<List<BleService>> discoverServices(String deviceId);
Future<List<BleService>> discoverServices(
String deviceId, bool withDescriptors);
Future<void> setNotifiable(String deviceId, String service,
String characteristic, BleInputProperty bleInputProperty);
@@ -62,10 +62,14 @@ class UniversalBleWeb extends UniversalBlePlatform {
}
@override
Future<List<BleService>> discoverServices(String deviceId) async =>
(await _getServices(deviceId))
.map((e) => e._bleService(deviceId))
.toList();
Future<List<BleService>> discoverServices(
String deviceId, bool withDescriptors) async {
List<BleService> services = [];
for (var service in await _getServices(deviceId)) {
services.add(await service._toBleService(deviceId, withDescriptors));
}
return services;
}
@override
Future<AvailabilityState> getBluetoothAvailabilityState() async {
@@ -536,24 +540,40 @@ class _UniversalWebBluetoothService {
return null;
}
BleService _bleService(String deviceId) => BleService(
service.uuid,
characteristics.map((e) {
return BleCharacteristic.withMetaData(
deviceId: deviceId,
serviceId: service.uuid,
uuid: e.uuid,
properties: [
if (e.properties.broadcast) CharacteristicProperty.broadcast,
if (e.properties.read) CharacteristicProperty.read,
if (e.properties.write) CharacteristicProperty.write,
if (e.properties.writeWithoutResponse)
CharacteristicProperty.writeWithoutResponse,
if (e.properties.notify) CharacteristicProperty.notify,
if (e.properties.indicate) CharacteristicProperty.indicate,
if (e.properties.authenticatedSignedWrites)
CharacteristicProperty.authenticatedSignedWrites,
]);
}).toList(),
);
Future<BleService> _toBleService(
String deviceId,
bool withDescriptors,
) async {
List<BleCharacteristic> bleCharacteristics = [];
for (var characteristic in characteristics) {
List<BleDescriptor> descriptors = [];
if (withDescriptors) {
try {
var bluetoothDescriptors = await characteristic.getDescriptors();
descriptors =
bluetoothDescriptors.map((e) => BleDescriptor(e.uuid)).toList();
} catch (_) {}
}
bleCharacteristics.add(BleCharacteristic.withMetaData(
deviceId: deviceId,
serviceId: service.uuid,
uuid: characteristic.uuid,
properties: [
if (characteristic.properties.broadcast)
CharacteristicProperty.broadcast,
if (characteristic.properties.read) CharacteristicProperty.read,
if (characteristic.properties.write) CharacteristicProperty.write,
if (characteristic.properties.writeWithoutResponse)
CharacteristicProperty.writeWithoutResponse,
if (characteristic.properties.notify) CharacteristicProperty.notify,
if (characteristic.properties.indicate)
CharacteristicProperty.indicate,
if (characteristic.properties.authenticatedSignedWrites)
CharacteristicProperty.authenticatedSignedWrites,
],
descriptors: descriptors,
));
}
return BleService(service.uuid, bleCharacteristics);
}
}