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
+1
View File
@@ -4,6 +4,7 @@
* Add `requestPermissions` api and auto ask permission on `startScan`
* `disconnect` now waits for disconnection confirmation before returning
* Improve Windows disconnection event handling and cleanup
* Add `withDescriptors` parameter in `discoverServices` API
## 0.21.1
* Fix device name resolution on Windows
@@ -229,20 +229,23 @@ data class UniversalBleService (
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBleCharacteristic (
val uuid: String,
val properties: List<Long>
val properties: List<Long>,
val descriptors: List<UniversalBleDescriptor>
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalBleCharacteristic {
val uuid = pigeonVar_list[0] as String
val properties = pigeonVar_list[1] as List<Long>
return UniversalBleCharacteristic(uuid, properties)
val descriptors = pigeonVar_list[2] as List<UniversalBleDescriptor>
return UniversalBleCharacteristic(uuid, properties, descriptors)
}
}
fun toList(): List<Any?> {
return listOf(
uuid,
properties,
descriptors,
)
}
override fun equals(other: Any?): Boolean {
@@ -257,6 +260,34 @@ data class UniversalBleCharacteristic (
override fun hashCode(): Int = toList().hashCode()
}
/** Generated class from Pigeon that represents data sent in messages. */
data class UniversalBleDescriptor (
val uuid: String
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): UniversalBleDescriptor {
val uuid = pigeonVar_list[0] as String
return UniversalBleDescriptor(uuid)
}
}
fun toList(): List<Any?> {
return listOf(
uuid,
)
}
override fun equals(other: Any?): Boolean {
if (other !is UniversalBleDescriptor) {
return false
}
if (this === other) {
return true
}
return UniversalBlePigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
/**
* Scan Filters
*
@@ -384,15 +415,20 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() {
}
133.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalScanFilter.fromList(it)
UniversalBleDescriptor.fromList(it)
}
}
134.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerDataFilter.fromList(it)
UniversalScanFilter.fromList(it)
}
}
135.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerDataFilter.fromList(it)
}
}
136.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
UniversalManufacturerData.fromList(it)
}
@@ -418,18 +454,22 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() {
stream.write(132)
writeValue(stream, value.toList())
}
is UniversalScanFilter -> {
is UniversalBleDescriptor -> {
stream.write(133)
writeValue(stream, value.toList())
}
is UniversalManufacturerDataFilter -> {
is UniversalScanFilter -> {
stream.write(134)
writeValue(stream, value.toList())
}
is UniversalManufacturerData -> {
is UniversalManufacturerDataFilter -> {
stream.write(135)
writeValue(stream, value.toList())
}
is UniversalManufacturerData -> {
stream.write(136)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
@@ -452,7 +492,7 @@ interface UniversalBlePlatformChannel {
fun connect(deviceId: String)
fun disconnect(deviceId: String)
fun setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Long, callback: (Result<Unit>) -> Unit)
fun discoverServices(deviceId: String, callback: (Result<List<UniversalBleService>>) -> Unit)
fun discoverServices(deviceId: String, withDescriptors: Boolean, callback: (Result<List<UniversalBleService>>) -> Unit)
fun readValue(deviceId: String, service: String, characteristic: String, callback: (Result<ByteArray>) -> Unit)
fun requestMtu(deviceId: String, expectedMtu: Long, callback: (Result<Long>) -> Unit)
fun writeValue(deviceId: String, service: String, characteristic: String, value: ByteArray, bleOutputProperty: Long, callback: (Result<Unit>) -> Unit)
@@ -657,7 +697,8 @@ interface UniversalBlePlatformChannel {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val deviceIdArg = args[0] as String
api.discoverServices(deviceIdArg) { result: Result<List<UniversalBleService>> ->
val withDescriptorsArg = args[1] as Boolean
api.discoverServices(deviceIdArg, withDescriptorsArg) { result: Result<List<UniversalBleService>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(UniversalBlePigeonUtils.wrapError(error))
@@ -315,6 +315,7 @@ fun Int.parseHciErrorCode(): String? {
// Future result classes
class DiscoverServicesFuture(
val deviceId: String,
val withDescriptors: Boolean,
val result: (Result<List<UniversalBleService>>) -> Unit,
)
@@ -276,12 +276,19 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
override fun discoverServices(
deviceId: String,
withDescriptors: Boolean,
callback: (Result<List<UniversalBleService>>) -> Unit,
) {
try {
val gatt = deviceId.toBluetoothGatt()
if (gatt.discoverServices()) {
discoverServicesFutureList.add(DiscoverServicesFuture(deviceId, callback))
discoverServicesFutureList.add(
DiscoverServicesFuture(
deviceId,
withDescriptors,
callback
)
)
} else {
callback(
Result.failure(
@@ -313,20 +320,22 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
return
}
setCachedServices(gatt.device.address, gatt.services.map { it.uuid.toString() })
val universalBleServices = gatt.services.map { service ->
UniversalBleService(
uuid = service.uuid.toString(),
characteristics = service.characteristics.map {
UniversalBleCharacteristic(
uuid = it.uuid.toString(),
properties = it.getPropertiesList()
)
}
)
}
discoverServicesFutureList.filter { it.deviceId == gatt.device.address }.forEach {
discoverServicesFutureList.remove(it)
it.result(Result.success(universalBleServices))
it.result(Result.success(gatt.services.map { service ->
UniversalBleService(
uuid = service.uuid.toString(),
characteristics = service.characteristics.map { char ->
UniversalBleCharacteristic(
uuid = char.uuid.toString(),
properties = char.getPropertiesList(),
descriptors = if (it.withDescriptors) char.descriptors.map { descriptor ->
UniversalBleDescriptor(descriptor.uuid.toString())
} else listOf()
)
}
)
}))
}
}
@@ -818,7 +827,10 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
if (gatt.discoverServices()) {
discoverServicesFutureList.add(
DiscoverServicesFuture(device.address) { uuids: Result<List<UniversalBleService>> ->
DiscoverServicesFuture(
device.address,
false
) { uuids: Result<List<UniversalBleService>> ->
if (uuids.isSuccess) {
updateCallback(uuids.getOrNull()?.map { it.uuid })
} else {
+43 -8
View File
@@ -275,22 +275,26 @@ struct UniversalBleService: Hashable {
struct UniversalBleCharacteristic: Hashable {
var uuid: String
var properties: [Int64]
var descriptors: [UniversalBleDescriptor]
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> UniversalBleCharacteristic? {
let uuid = pigeonVar_list[0] as! String
let properties = pigeonVar_list[1] as! [Int64]
let descriptors = pigeonVar_list[2] as! [UniversalBleDescriptor]
return UniversalBleCharacteristic(
uuid: uuid,
properties: properties
properties: properties,
descriptors: descriptors
)
}
func toList() -> [Any?] {
return [
uuid,
properties,
descriptors,
]
}
static func == (lhs: UniversalBleCharacteristic, rhs: UniversalBleCharacteristic) -> Bool {
@@ -300,6 +304,31 @@ struct UniversalBleCharacteristic: Hashable {
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalBleDescriptor: Hashable {
var uuid: String
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> UniversalBleDescriptor? {
let uuid = pigeonVar_list[0] as! String
return UniversalBleDescriptor(
uuid: uuid
)
}
func toList() -> [Any?] {
return [
uuid
]
}
static func == (lhs: UniversalBleDescriptor, rhs: UniversalBleDescriptor) -> Bool {
return deepEqualsUniversalBle(lhs.toList(), rhs.toList()) }
func hash(into hasher: inout Hasher) {
deepHashUniversalBle(value: toList(), hasher: &hasher)
}
}
/// Scan Filters
///
/// Generated class from Pigeon that represents data sent in messages.
@@ -413,10 +442,12 @@ private class UniversalBlePigeonCodecReader: FlutterStandardReader {
case 132:
return UniversalBleCharacteristic.fromList(self.readValue() as! [Any?])
case 133:
return UniversalScanFilter.fromList(self.readValue() as! [Any?])
return UniversalBleDescriptor.fromList(self.readValue() as! [Any?])
case 134:
return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?])
return UniversalScanFilter.fromList(self.readValue() as! [Any?])
case 135:
return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?])
case 136:
return UniversalManufacturerData.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
@@ -438,15 +469,18 @@ private class UniversalBlePigeonCodecWriter: FlutterStandardWriter {
} else if let value = value as? UniversalBleCharacteristic {
super.writeByte(132)
super.writeValue(value.toList())
} else if let value = value as? UniversalScanFilter {
} else if let value = value as? UniversalBleDescriptor {
super.writeByte(133)
super.writeValue(value.toList())
} else if let value = value as? UniversalManufacturerDataFilter {
} else if let value = value as? UniversalScanFilter {
super.writeByte(134)
super.writeValue(value.toList())
} else if let value = value as? UniversalManufacturerData {
} else if let value = value as? UniversalManufacturerDataFilter {
super.writeByte(135)
super.writeValue(value.toList())
} else if let value = value as? UniversalManufacturerData {
super.writeByte(136)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
@@ -482,7 +516,7 @@ protocol UniversalBlePlatformChannel {
func connect(deviceId: String) throws
func disconnect(deviceId: String) throws
func setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void)
func discoverServices(deviceId: String, completion: @escaping (Result<[UniversalBleService], Error>) -> Void)
func discoverServices(deviceId: String, withDescriptors: Bool, completion: @escaping (Result<[UniversalBleService], Error>) -> Void)
func readValue(deviceId: String, service: String, characteristic: String, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void)
func requestMtu(deviceId: String, expectedMtu: Int64, completion: @escaping (Result<Int64, Error>) -> Void)
func writeValue(deviceId: String, service: String, characteristic: String, value: FlutterStandardTypedData, bleOutputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void)
@@ -657,7 +691,8 @@ class UniversalBlePlatformChannelSetup {
discoverServicesChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let deviceIdArg = args[0] as! String
api.discoverServices(deviceId: deviceIdArg) { result in
let withDescriptorsArg = args[1] as! Bool
api.discoverServices(deviceId: deviceIdArg, withDescriptors: withDescriptorsArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
@@ -0,0 +1,217 @@
import CoreBluetooth
import Foundation
#if os(iOS)
import Flutter
#elseif os(OSX)
import FlutterMacOS
#endif
/// Handles asynchronous service discovery for a BLE peripheral.
/// Manages the complete discovery flow: services -> characteristics -> descriptors
class UniversalBleAsyncServiceDiscovery: NSObject {
private let peripheral: CBPeripheral
private let deviceId: String
private let completion: (Result<[UniversalBleService], Error>) -> Void
private var discoveredServicesProgressMap: [UniversalBleService] = []
private var discoveredDescriptorsSet: Set<String> = []
private var expectedCharacteristicsCountMap: [String: Int] = [:]
private var isDiscoveryInProgress = false
private var withDescriptors: Bool
init(peripheral: CBPeripheral, deviceId: String, withDescriptors: Bool, completion: @escaping (Result<[UniversalBleService], Error>) -> Void) {
self.peripheral = peripheral
self.deviceId = deviceId
self.completion = completion
self.withDescriptors = withDescriptors
super.init()
}
/// Starts the service discovery process
func startDiscovery() {
guard !isDiscoveryInProgress else {
print("Service discovery already in progress for device: \(deviceId)")
return
}
isDiscoveryInProgress = true
// Check if services are already cached
if let cachedServices = peripheral.services, !cachedServices.isEmpty {
handleServicesDiscovered(cachedServices)
} else {
peripheral.discoverServices(nil)
}
}
/// Cleans up discovery state
func cleanup() {
isDiscoveryInProgress = false
discoveredServicesProgressMap.removeAll()
discoveredDescriptorsSet.removeAll()
expectedCharacteristicsCountMap.removeAll()
}
private func handleServicesDiscovered(_ services: [CBService]) {
discoveredServicesProgressMap = services.map { UniversalBleService(uuid: $0.uuid.uuidString, characteristics: nil) }
discoveredDescriptorsSet = Set<String>()
expectedCharacteristicsCountMap = [:]
// If no services, complete discovery immediately
guard !services.isEmpty else {
checkForDiscoveryCompletion()
return
}
// Discover characteristics for each service
for service in services {
if let cachedChar = service.characteristics, !cachedChar.isEmpty {
// Characteristics already cached, process them
processCharacteristicsForService(service)
} else {
// Need to discover characteristics
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
private func processCharacteristicsForService(_ service: CBService) {
let serviceUuid = service.uuid.uuidString
guard let characteristics = service.characteristics else {
// Service has no characteristics, mark as complete
expectedCharacteristicsCountMap[serviceUuid] = 0
if let index = discoveredServicesProgressMap.firstIndex(where: { $0.uuid == serviceUuid }) {
discoveredServicesProgressMap[index] = UniversalBleService(uuid: serviceUuid, characteristics: [])
}
checkForDiscoveryCompletion()
return
}
// Store expected characteristic count for this service
expectedCharacteristicsCountMap[serviceUuid] = characteristics.count
// If no characteristics, mark service as complete
if characteristics.isEmpty {
if let index = discoveredServicesProgressMap.firstIndex(where: { $0.uuid == serviceUuid }) {
discoveredServicesProgressMap[index] = UniversalBleService(uuid: serviceUuid, characteristics: [])
}
checkForDiscoveryCompletion()
return
}
if withDescriptors {
for characteristic in characteristics {
if let cachedDescriptors = characteristic.descriptors, !cachedDescriptors.isEmpty {
handleDescriptorsDiscovered(for: characteristic)
} else {
peripheral.discoverDescriptors(for: characteristic)
}
}
} else {
if let index = discoveredServicesProgressMap.firstIndex(where: { $0.uuid == serviceUuid }) {
discoveredServicesProgressMap[index] = UniversalBleService(
uuid: serviceUuid,
characteristics: characteristics.map {
UniversalBleCharacteristic(
uuid: $0.uuid.uuidString,
properties: $0.properties.toCharacteristicProperty,
descriptors: []
)
}
)
}
checkForDiscoveryCompletion()
}
}
private func handleDescriptorsDiscovered(for characteristic: CBCharacteristic) {
guard let service = characteristic.service else {
return
}
let serviceUuid = service.uuid.uuidString
let characteristicUuid = characteristic.uuid.uuidString
let characteristicKey = "\(serviceUuid):\(characteristicUuid)"
// Mark this characteristic's descriptors as discovered
discoveredDescriptorsSet.insert(characteristicKey)
// Get expected characteristic count for this service
guard let expectedCount = expectedCharacteristicsCountMap[serviceUuid] else {
return
}
// Check if all characteristics for this service have had their descriptors discovered
guard let allCharacteristics = service.characteristics else {
return
}
let discoveredCount = allCharacteristics.filter { char in
let key = "\(serviceUuid):\(char.uuid.uuidString)"
return discoveredDescriptorsSet.contains(key)
}.count
// Only update the service when all characteristics have descriptors discovered
if discoveredCount == expectedCount {
var universalBleCharacteristicsList: [UniversalBleCharacteristic] = []
for characteristic in allCharacteristics {
universalBleCharacteristicsList.append(
UniversalBleCharacteristic(
uuid: characteristic.uuid.uuidString,
properties: characteristic.properties.toCharacteristicProperty,
descriptors: (characteristic.descriptors ?? []).map { UniversalBleDescriptor(uuid: $0.uuid.uuidString) }
)
)
}
if let index = discoveredServicesProgressMap.firstIndex(where: { $0.uuid == serviceUuid }) {
discoveredServicesProgressMap[index] = UniversalBleService(uuid: serviceUuid, characteristics: universalBleCharacteristicsList)
}
checkForDiscoveryCompletion()
}
}
private func checkForDiscoveryCompletion() {
// Check if all services have been fully discovered (all characteristics with all descriptors)
guard discoveredServicesProgressMap.allSatisfy({ $0.characteristics != nil }) else {
return
}
completion(.success(discoveredServicesProgressMap))
cleanup()
}
}
// These methods are called by the main plugin class when it receives delegate callbacks
extension UniversalBleAsyncServiceDiscovery {
func handleDidDiscoverServices(_ peripheral: CBPeripheral, error: Error?) {
guard error == nil else {
completion(.failure(error!))
cleanup()
return
}
guard let services = peripheral.services else {
completion(.success([]))
cleanup()
return
}
handleServicesDiscovered(services)
}
func handleDidDiscoverCharacteristicsFor(_: CBPeripheral, service: CBService, error: Error?) {
guard error == nil else {
completion(.failure(error!))
cleanup()
return
}
processCharacteristicsForService(service)
}
func handleDidDiscoverDescriptorsFor(_: CBPeripheral, characteristic: CBCharacteristic, error: Error?) {
guard error == nil else {
completion(.failure(error!))
cleanup()
return
}
handleDescriptorsDiscovered(for: characteristic)
}
}
+32 -55
View File
@@ -34,7 +34,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
private lazy var manager: CBCentralManager = .init(delegate: self, queue: nil)
private var availabilityStateUpdateHandlers: [(Result<Int64, Error>) -> Void] = []
private var requestPermissionStateUpdateHandlers: [(Result<Void, Error>) -> Void] = []
private var discoveredServicesProgressMap: [String: [UniversalBleService]] = [:]
private var activeServiceDiscoveries: [String: UniversalBleAsyncServiceDiscovery] = [:]
private var characteristicReadFutures = [CharacteristicReadFuture]()
private var characteristicWriteFutures = [CharacteristicWriteFuture]()
private var characteristicWriteWithoutResponseFutures = [CharacteristicWriteFuture]()
@@ -204,45 +204,46 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
}
return false
}
discoveredServicesProgressMap[deviceId] = nil
activeServiceDiscoveries[deviceId]?.cleanup()
activeServiceDiscoveries[deviceId] = nil
}
func discoverServices(deviceId: String, completion: @escaping (Result<[UniversalBleService], Error>) -> Void) {
func discoverServices(deviceId: String, withDescriptors: Bool, completion: @escaping (Result<[UniversalBleService], Error>) -> Void) {
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
completion(
Result.failure(createFlutterError(code: .deviceNotFound, message: "Unknown deviceId:\(self)"))
Result.failure(createFlutterError(code: .deviceNotFound, message: "Unknown deviceId:\(deviceId)"))
)
return
}
if discoveredServicesProgressMap[deviceId] != nil {
// Check if discovery is already in progress
if activeServiceDiscoveries[deviceId] != nil {
print("Services discovery already in progress for :\(deviceId), waiting for completion.")
discoverServicesFutures.append(DiscoverServicesFuture(deviceId: deviceId, result: completion))
return
}
if let cachedServices = peripheral.services {
// If services already discovered no need to discover again
if !cachedServices.isEmpty {
// print("Services already cached for this peripheral")
discoverServicesFutures.append(DiscoverServicesFuture(deviceId: deviceId, result: completion))
self.peripheral(peripheral, didDiscoverServices: nil)
return
let wrappedCompletion: (Result<[UniversalBleService], Error>) -> Void = { result in
completion(result)
self.discoverServicesFutures.removeAll { future in
if future.deviceId == deviceId {
future.result(result)
return true
}
return false
}
self.activeServiceDiscoveries[deviceId] = nil
}
peripheral.discoverServices(nil)
discoverServicesFutures.append(DiscoverServicesFuture(deviceId: deviceId, result: completion))
}
let discovery = UniversalBleAsyncServiceDiscovery(
peripheral: peripheral,
deviceId: deviceId,
withDescriptors: withDescriptors,
completion: wrappedCompletion
)
private func onServicesDiscovered(deviceId: String, services: [UniversalBleService]) {
discoverServicesFutures.removeAll { future in
if future.deviceId == deviceId {
future.result(Result.success(services))
return true
}
return false
}
activeServiceDiscoveries[deviceId] = discovery
discovery.startDiscovery()
}
func setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Int64, completion: @escaping (Result<Void, any Error>) -> Void) {
@@ -432,40 +433,16 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
cleanUpConnection(deviceId: peripheral.uuid.uuidString)
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices _: Error?) {
let deviceId = peripheral.identifier.uuidString
guard let services = peripheral.services else {
onServicesDiscovered(deviceId: deviceId, services: [])
return
}
discoveredServicesProgressMap[deviceId] = services.map { UniversalBleService(uuid: $0.uuid.uuidString, characteristics: nil) }
for service in services {
if let cachedChar = service.characteristics {
if !cachedChar.isEmpty {
self.peripheral(peripheral, didDiscoverCharacteristicsFor: service, error: nil)
continue
}
}
peripheral.discoverCharacteristics(nil, for: service)
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
activeServiceDiscoveries[peripheral.identifier.uuidString]?.handleDidDiscoverServices(peripheral, error: error)
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error _: Error?) {
let deviceId = peripheral.identifier.uuidString
var universalBleCharacteristicsList: [UniversalBleCharacteristic] = []
for characteristic in service.characteristics ?? [] {
universalBleCharacteristicsList.append(
UniversalBleCharacteristic(uuid: characteristic.uuid.uuidString, properties: characteristic.properties.toCharacteristicProperty))
}
// Update discoveredServicesProgressMap
if let index = discoveredServicesProgressMap[deviceId]?.firstIndex(where: { $0.uuid == service.uuid.uuidString }) {
discoveredServicesProgressMap[deviceId]?[index] = UniversalBleService(uuid: service.uuid.uuidString, characteristics: universalBleCharacteristicsList)
}
// Check if all services and their characteristics have been discovered
if discoveredServicesProgressMap[deviceId]?.allSatisfy({ $0.characteristics != nil }) ?? false {
onServicesDiscovered(deviceId: deviceId, services: discoveredServicesProgressMap[deviceId] ?? [])
discoveredServicesProgressMap[deviceId] = nil
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
activeServiceDiscoveries[peripheral.identifier.uuidString]?.handleDidDiscoverCharacteristicsFor(peripheral, service: service, error: error)
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
activeServiceDiscoveries[peripheral.identifier.uuidString]?.handleDidDiscoverDescriptorsFor(peripheral, characteristic: characteristic, error: error)
}
public func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
+3 -2
View File
@@ -20,7 +20,7 @@ class MockUniversalBle extends UniversalBlePlatform {
CharacteristicProperty.read,
CharacteristicProperty.write,
CharacteristicProperty.notify,
]),
], []),
]);
@override
@@ -53,7 +53,8 @@ class MockUniversalBle extends UniversalBlePlatform {
}
@override
Future<List<BleService>> discoverServices(String deviceId) async {
Future<List<BleService>> discoverServices(
String deviceId, bool withDescriptors) async {
return [_mockService];
}
@@ -98,7 +98,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
const webWarning =
"Note: Only services added in ScanFilter or WebOptions will be discovered";
try {
var services = await bleDevice.discoverServices();
var services = await bleDevice.discoverServices(withDescriptors: false);
debugPrint('${services.length} services discovered');
debugPrint(services.toString());
setState(() {
@@ -61,7 +61,11 @@ class ServicesListWidget extends StatelessWidget {
),
Text(
"Properties: ${e.properties.map((e) => e.name)}",
)
),
if (e.descriptors.isNotEmpty)
Text(
"Descriptors: ${e.descriptors.map((e) => e.uuid).join(', ')}",
)
],
),
),
@@ -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);
}
}
+11 -2
View File
@@ -52,7 +52,10 @@ abstract class UniversalBlePlatformChannel {
);
@async
List<UniversalBleService> discoverServices(String deviceId);
List<UniversalBleService> discoverServices(
String deviceId,
bool withDescriptors,
);
@async
Uint8List readValue(
@@ -138,7 +141,13 @@ class UniversalBleService {
class UniversalBleCharacteristic {
String uuid;
List<int> properties;
UniversalBleCharacteristic(this.uuid, this.properties);
List<UniversalBleDescriptor> descriptors;
UniversalBleCharacteristic(this.uuid, this.properties, this.descriptors);
}
class UniversalBleDescriptor {
String uuid;
UniversalBleDescriptor(this.uuid);
}
/// Scan Filters
+3 -1
View File
@@ -12,6 +12,7 @@ BleCharacteristic mockBleCharacteristic = BleCharacteristic.withMetaData(
serviceId: serviceId,
uuid: characteristicId,
properties: CharacteristicProperty.values,
descriptors: [],
);
BleService mockBleService = BleService(serviceId, [mockBleCharacteristic]);
@@ -86,7 +87,8 @@ class _UniversalBleMock extends UniversalBlePlatformMock {
Uint8List? charValue;
@override
Future<List<BleService>> discoverServices(String deviceId) async {
Future<List<BleService>> discoverServices(
String deviceId, bool withDescriptors) async {
return <BleService>[mockBleService];
}
+2 -1
View File
@@ -18,7 +18,8 @@ abstract class UniversalBlePlatformMock extends UniversalBlePlatform {
}
@override
Future<List<BleService>> discoverServices(String deviceId) {
Future<List<BleService>> discoverServices(
String deviceId, bool withDescriptors) {
throw UnimplementedError();
}
+64 -10
View File
@@ -214,9 +214,11 @@ UniversalBleService UniversalBleService::FromEncodableList(const EncodableList&
UniversalBleCharacteristic::UniversalBleCharacteristic(
const std::string& uuid,
const EncodableList& properties)
const EncodableList& properties,
const EncodableList& descriptors)
: uuid_(uuid),
properties_(properties) {}
properties_(properties),
descriptors_(descriptors) {}
const std::string& UniversalBleCharacteristic::uuid() const {
return uuid_;
@@ -236,18 +238,56 @@ void UniversalBleCharacteristic::set_properties(const EncodableList& value_arg)
}
const EncodableList& UniversalBleCharacteristic::descriptors() const {
return descriptors_;
}
void UniversalBleCharacteristic::set_descriptors(const EncodableList& value_arg) {
descriptors_ = value_arg;
}
EncodableList UniversalBleCharacteristic::ToEncodableList() const {
EncodableList list;
list.reserve(2);
list.reserve(3);
list.push_back(EncodableValue(uuid_));
list.push_back(EncodableValue(properties_));
list.push_back(EncodableValue(descriptors_));
return list;
}
UniversalBleCharacteristic UniversalBleCharacteristic::FromEncodableList(const EncodableList& list) {
UniversalBleCharacteristic decoded(
std::get<std::string>(list[0]),
std::get<EncodableList>(list[1]));
std::get<EncodableList>(list[1]),
std::get<EncodableList>(list[2]));
return decoded;
}
// UniversalBleDescriptor
UniversalBleDescriptor::UniversalBleDescriptor(const std::string& uuid)
: uuid_(uuid) {}
const std::string& UniversalBleDescriptor::uuid() const {
return uuid_;
}
void UniversalBleDescriptor::set_uuid(std::string_view value_arg) {
uuid_ = value_arg;
}
EncodableList UniversalBleDescriptor::ToEncodableList() const {
EncodableList list;
list.reserve(1);
list.push_back(EncodableValue(uuid_));
return list;
}
UniversalBleDescriptor UniversalBleDescriptor::FromEncodableList(const EncodableList& list) {
UniversalBleDescriptor decoded(
std::get<std::string>(list[0]));
return decoded;
}
@@ -439,12 +479,15 @@ EncodableValue PigeonInternalCodecSerializer::ReadValueOfType(
return CustomEncodableValue(UniversalBleCharacteristic::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 133: {
return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalBleDescriptor::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 134: {
return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 135: {
return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 136: {
return CustomEncodableValue(UniversalManufacturerData::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
default:
@@ -476,18 +519,23 @@ void PigeonInternalCodecSerializer::WriteValue(
WriteValue(EncodableValue(std::any_cast<UniversalBleCharacteristic>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalScanFilter)) {
if (custom_value->type() == typeid(UniversalBleDescriptor)) {
stream->WriteByte(133);
WriteValue(EncodableValue(std::any_cast<UniversalBleDescriptor>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalScanFilter)) {
stream->WriteByte(134);
WriteValue(EncodableValue(std::any_cast<UniversalScanFilter>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalManufacturerDataFilter)) {
stream->WriteByte(134);
stream->WriteByte(135);
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerDataFilter>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalManufacturerData)) {
stream->WriteByte(135);
stream->WriteByte(136);
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerData>(*custom_value).ToEncodableList()), stream);
return;
}
@@ -788,7 +836,13 @@ void UniversalBlePlatformChannel::SetUp(
return;
}
const auto& device_id_arg = std::get<std::string>(encodable_device_id_arg);
api->DiscoverServices(device_id_arg, [reply](ErrorOr<EncodableList>&& output) {
const auto& encodable_with_descriptors_arg = args.at(1);
if (encodable_with_descriptors_arg.IsNull()) {
reply(WrapError("with_descriptors_arg unexpectedly null."));
return;
}
const auto& with_descriptors_arg = std::get<bool>(encodable_with_descriptors_arg);
api->DiscoverServices(device_id_arg, with_descriptors_arg, [reply](ErrorOr<EncodableList>&& output) {
if (output.has_error()) {
reply(WrapError(output.error()));
return;
+26 -1
View File
@@ -211,7 +211,8 @@ class UniversalBleCharacteristic {
// Constructs an object setting all fields.
explicit UniversalBleCharacteristic(
const std::string& uuid,
const flutter::EncodableList& properties);
const flutter::EncodableList& properties,
const flutter::EncodableList& descriptors);
const std::string& uuid() const;
void set_uuid(std::string_view value_arg);
@@ -219,6 +220,9 @@ class UniversalBleCharacteristic {
const flutter::EncodableList& properties() const;
void set_properties(const flutter::EncodableList& value_arg);
const flutter::EncodableList& descriptors() const;
void set_descriptors(const flutter::EncodableList& value_arg);
private:
static UniversalBleCharacteristic FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
@@ -227,6 +231,26 @@ class UniversalBleCharacteristic {
friend class PigeonInternalCodecSerializer;
std::string uuid_;
flutter::EncodableList properties_;
flutter::EncodableList descriptors_;
};
// Generated class from Pigeon that represents data sent in messages.
class UniversalBleDescriptor {
public:
// Constructs an object setting all fields.
explicit UniversalBleDescriptor(const std::string& uuid);
const std::string& uuid() const;
void set_uuid(std::string_view value_arg);
private:
static UniversalBleDescriptor FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
friend class UniversalBlePlatformChannel;
friend class UniversalBleCallbackChannel;
friend class PigeonInternalCodecSerializer;
std::string uuid_;
};
@@ -366,6 +390,7 @@ class UniversalBlePlatformChannel {
std::function<void(std::optional<FlutterError> reply)> result) = 0;
virtual void DiscoverServices(
const std::string& device_id,
bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) = 0;
virtual void ReadValue(
const std::string& device_id,
+82 -59
View File
@@ -121,9 +121,9 @@ void UniversalBlePlugin::DisableBluetooth(
void UniversalBlePlugin::RequestPermissions(
bool with_android_fine_location,
std::function<void(std::optional<FlutterError> reply)> result) {
// Windows does not require runtime permissions for Bluetooth
result(std::nullopt);
return;
// Windows does not require runtime permissions for Bluetooth
result(std::nullopt);
return;
}
std::optional<FlutterError>
@@ -223,8 +223,10 @@ std::optional<FlutterError> UniversalBlePlugin::StopScan() {
ErrorOr<bool> UniversalBlePlugin::IsScanning() {
if (bluetooth_le_watcher_ != nullptr) {
try {
return bluetooth_le_watcher_.Status() == BluetoothLEAdvertisementWatcherStatus::Started;
} catch (...) {}
return bluetooth_le_watcher_.Status() ==
BluetoothLEAdvertisementWatcherStatus::Started;
} catch (...) {
}
}
return false;
}
@@ -269,24 +271,9 @@ UniversalBlePlugin::Disconnect(const std::string &device_id) {
}
void UniversalBlePlugin::DiscoverServices(
const std::string &device_id,
const std::string &device_id, bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) {
try {
const auto it = connected_devices_.find(str_to_mac_address(device_id));
if (it == connected_devices_.end()) {
result(create_flutter_error(UniversalBleErrorCode::kDeviceNotFound,
"Unknown devicesId:" + device_id));
return;
}
auto device_agent = *it->second;
DiscoverServicesAsync(device_agent, result);
} catch (const FlutterError &err) {
return result(err);
} catch (...) {
std::cout << "DiscoverServicesLog: Unknown error" << std::endl;
return result(create_flutter_error(UniversalBleErrorCode::kUnknownError,
"Unknown error"));
}
DiscoverServicesAsync(device_id, with_descriptors, result);
}
void UniversalBlePlugin::SetNotifiable(
@@ -369,7 +356,8 @@ void UniversalBlePlugin::WriteValue(
if ((properties & GattCharacteristicProperties::WriteWithoutResponse) ==
GattCharacteristicProperties::None) {
result(create_flutter_error(
UniversalBleErrorCode::kCharacteristicDoesNotSupportWriteWithoutResponse,
UniversalBleErrorCode::
kCharacteristicDoesNotSupportWriteWithoutResponse,
"Characteristic does not support WriteWithoutResponse"));
return;
}
@@ -384,22 +372,22 @@ void UniversalBlePlugin::WriteValue(
}
gatt_characteristic.WriteValueAsync(from_bytevc(value), write_option)
.Completed(
[&, result](IAsyncOperation<GattCommunicationStatus> const &sender,
AsyncStatus const args) {
if (args == AsyncStatus::Error) {
result(create_flutter_error(UniversalBleErrorCode::kFailed,
"Encountered an error."));
return;
}
.Completed([&, result](
IAsyncOperation<GattCommunicationStatus> const &sender,
AsyncStatus const args) {
if (args == AsyncStatus::Error) {
result(create_flutter_error(UniversalBleErrorCode::kFailed,
"Encountered an error."));
return;
}
const auto status = sender.GetResults();
if (status != GattCommunicationStatus::Success) {
result(create_flutter_error_from_gatt_communication_status(status));
} else {
result(std::nullopt);
}
});
const auto status = sender.GetResults();
if (status != GattCommunicationStatus::Success) {
result(create_flutter_error_from_gatt_communication_status(status));
} else {
result(std::nullopt);
}
});
} catch (const FlutterError &err) {
result(err);
} catch (...) {
@@ -548,7 +536,8 @@ fire_and_forget UniversalBlePlugin::PairAsync(
result(is_paired);
const std::string *error_msg = nullptr;
const auto error_str = device_pairing_result_to_string(pair_result.Status());
const auto error_str =
device_pairing_result_to_string(pair_result.Status());
if (error_str.has_value()) {
error_msg = &error_str.value();
}
@@ -1039,18 +1028,18 @@ void UniversalBlePlugin::CleanConnection(const uint64_t bluetooth_address) {
}
}
void UniversalBlePlugin::DisposeServices(const std::unique_ptr<BluetoothDeviceAgent> &device_agent)
{
for (auto& [service_id, service] : device_agent->gatt_map) {
for (auto& [char_id, characteristic] : service.characteristics) {
if (characteristic.subscription_token.has_value()) {
characteristic.obj.ValueChanged(
characteristic.subscription_token.value());
characteristic.subscription_token = std::nullopt;
}
}
void UniversalBlePlugin::DisposeServices(
const std::unique_ptr<BluetoothDeviceAgent> &device_agent) {
for (auto &[service_id, service] : device_agent->gatt_map) {
for (auto &[char_id, characteristic] : service.characteristics) {
if (characteristic.subscription_token.has_value()) {
characteristic.obj.ValueChanged(
characteristic.subscription_token.value());
characteristic.subscription_token = std::nullopt;
}
}
device_agent->gatt_map.clear();
}
device_agent->gatt_map.clear();
}
fire_and_forget UniversalBlePlugin::GetSystemDevicesAsync(
@@ -1108,21 +1097,49 @@ fire_and_forget UniversalBlePlugin::GetSystemDevicesAsync(
}
}
void UniversalBlePlugin::DiscoverServicesAsync(
BluetoothDeviceAgent &bluetooth_device_agent,
const std::function<void(ErrorOr<flutter::EncodableList> reply)> &result) {
fire_and_forget UniversalBlePlugin::DiscoverServicesAsync(
const std::string &device_id, bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) {
try {
const auto it = connected_devices_.find(str_to_mac_address(device_id));
if (it == connected_devices_.end()) {
result(create_flutter_error(UniversalBleErrorCode::kDeviceNotFound,
"Unknown devicesId:" + device_id));
co_return;
}
auto universal_services = flutter::EncodableList();
for (auto &[service_id, service] : bluetooth_device_agent.gatt_map) {
for (auto &[service_id, service] : it->second->gatt_map) {
flutter::EncodableList universal_characteristics;
for (auto [char_id, characteristic] : service.characteristics) {
auto &c = characteristic.obj;
const auto properties_value = c.CharacteristicProperties();
auto properties = properties_to_flutter_encodable(properties_value);
universal_characteristics.push_back(flutter::CustomEncodableValue(
UniversalBleCharacteristic(to_uuidstr(c.Uuid()), properties)));
auto descriptors = flutter::EncodableList();
if (with_descriptors) {
try {
// move continuation to background and execute in safe thread
// context
co_await winrt::resume_background();
auto descriptor_result =
co_await c.GetDescriptorsAsync(BluetoothCacheMode::Cached);
if (descriptor_result.Status() ==
GattCommunicationStatus::Success) {
auto descriptors_list = descriptor_result.Descriptors();
for (auto &&descriptor : descriptors_list) {
descriptors.push_back(flutter::CustomEncodableValue(
UniversalBleDescriptor(to_uuidstr(descriptor.Uuid()))));
}
}
} catch (...) {
std::cout << "DiscoverServicesAsync: failed to get descriptors for "
"characteristic: "
<< std::endl;
}
}
universal_characteristics.push_back(
flutter::CustomEncodableValue(UniversalBleCharacteristic(
to_uuidstr(c.Uuid()), properties, descriptors)));
}
auto universal_ble_service =
@@ -1132,10 +1149,16 @@ void UniversalBlePlugin::DiscoverServicesAsync(
flutter::CustomEncodableValue(universal_ble_service));
}
result(universal_services);
} catch (const hresult_error &err) {
const int error_code = err.code();
result(create_flutter_error(UniversalBleErrorCode::kFailed,
"DiscoverServicesAsync failed",
std::to_string(error_code)));
} catch (const FlutterError &err) {
result(err);
} catch (...) {
result(create_flutter_error(UniversalBleErrorCode::kUnknownError,
"Unknown error"));
std::cout << "DiscoverServiceError: Unknown error" << '\n';
}
}
+4 -1
View File
@@ -134,8 +134,10 @@ namespace universal_ble
std::vector<std::string> with_services,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result);
static fire_and_forget IsPairedAsync(const std::string& device_id, std::function<void(ErrorOr<bool> reply)> result);
fire_and_forget DiscoverServicesAsync(const std::string &device_id,
bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result);
static void DiscoverServicesAsync(BluetoothDeviceAgent& bluetooth_device_agent, const std::function<void(ErrorOr<flutter::EncodableList> reply)>&);
void PairingRequestedHandler(DeviceInformationCustomPairing sender, const DevicePairingRequestedEventArgs& event_args);
void RadioStateChanged(const Radio& sender, const IInspectable&);
@@ -167,6 +169,7 @@ namespace universal_ble
std::function<void(std::optional<FlutterError> reply)> result) override;
void DiscoverServices(
const std::string &device_id,
bool with_descriptors,
std::function<void(ErrorOr<flutter::EncodableList> reply)> result) override;
void SetNotifiable(
const std::string &device_id,