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:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user