Skip to content

Commit c808651

Browse files
committed
android/ios: more reliable discovery of bonded devices
1 parent 51c5bc0 commit c808651

File tree

6 files changed

+101
-40
lines changed

6 files changed

+101
-40
lines changed

android/src/main/kotlin/com/viam/ble/CentralManager.kt

+38
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,43 @@ class CentralManager(
5656
mustBePoweredOn()
5757
withContext(Dispatchers.IO) {
5858
scanMutex.withLock {
59+
// Android excludes bonded devices from showing up in advertisements. So we need
60+
// to connect to it in order to check out its services. We'll disconnect if it's
61+
// of no use to us.
62+
btMan.adapter.bondedDevices.forEach { device ->
63+
CoroutineScope(Dispatchers.IO).launch {
64+
for (i in 1..3) {
65+
try {
66+
Log.d(
67+
TAG,
68+
"connecting to bonded device ${device.name} ${device.address} to see if it has desired service(s)",
69+
)
70+
val connectedDevice = connectToDevice(device.address)
71+
val connectedDeviceServiceIds = connectedDevice.map { it["id"] as String }
72+
if (connectedDeviceServiceIds.intersect(serviceIds.map(String::lowercase).toSet()).isNotEmpty()) {
73+
Log.d(TAG, "bonded device ${device.address} contains a desired service id")
74+
_scanForPeripheralFlow.emit(
75+
Result.success(
76+
hashMapOf(
77+
"id" to device.address,
78+
"name" to device.name,
79+
"service_ids" to device.uuids?.map { toString() },
80+
),
81+
),
82+
)
83+
break
84+
} else {
85+
Log.d(TAG, "bonded device ${device.address} is not useful to us")
86+
disconnectFromDevice(device.address)
87+
}
88+
} catch (e: Throwable) {
89+
Log.d(TAG, "failed to connect to bonded device", e)
90+
}
91+
delay(5000)
92+
}
93+
}
94+
}
95+
5996
if (isScanning) {
6097
return@withContext
6198
}
@@ -90,6 +127,7 @@ class CentralManager(
90127
}.toMutableList()
91128
}
92129
lastNScans.add(now)
130+
Log.d(TAG, "requesting scan of $serviceIds")
93131
btMan.adapter.bluetoothLeScanner.startScan(
94132
serviceIds.map {
95133
ScanFilter

android/src/main/kotlin/com/viam/ble/Peripheral.kt

+48-32
Original file line numberDiff line numberDiff line change
@@ -210,17 +210,29 @@ class Peripheral(
210210
gatt: BluetoothGatt?,
211211
status: Int,
212212
) {
213-
if (status == BluetoothGatt.GATT_SUCCESS) {
213+
if (status == BluetoothGatt.GATT_SUCCESS && gatt != null) {
214214
val charsToRead =
215-
gatt?.services?.flatMap { svc ->
216-
svc.characteristics.filter { char ->
217-
char.properties and BluetoothGattCharacteristic.PROPERTY_READ != 0
215+
gatt.services?.flatMap { svc ->
216+
svc.characteristics.filter filter@{ char ->
217+
if (char.properties and BluetoothGattCharacteristic.PROPERTY_READ != 0) {
218+
return@filter true
219+
}
220+
val charUUID = char.uuid.toString().lowercase()
221+
val charSvcUUID =
222+
char.service.uuid
223+
.toString()
224+
.lowercase()
225+
if (!discoveredCharacteristics.containsKey(charSvcUUID)) {
226+
discoveredCharacteristics[charSvcUUID] = mutableSetOf()
227+
}
228+
discoveredCharacteristics[charSvcUUID]?.add(charUUID)
229+
230+
return@filter false
218231
}
219232
}
220233
neededCharDoneCount = charsToRead?.size ?: 0
221234
if (neededCharDoneCount == 0) {
222-
connectedContinuation?.resume(Unit)
223-
connectedContinuation = null
235+
serviceDiscoveryDone(gatt)
224236
return
225237
}
226238
CoroutineScope(Dispatchers.IO).launch {
@@ -238,6 +250,34 @@ class Peripheral(
238250
}
239251
}
240252

253+
fun serviceDiscoveryDone(gatt: BluetoothGatt) {
254+
_discoveredServices =
255+
gatt.services
256+
.filter { discoveredCharacteristics.containsKey(it.uuid.toString().lowercase()) }
257+
.map { svc ->
258+
hashMapOf(
259+
"id" to svc.uuid.toString().lowercase(),
260+
"characteristics" to
261+
svc.characteristics
262+
.filter {
263+
discoveredCharacteristics[
264+
it.service.uuid
265+
.toString()
266+
.lowercase(),
267+
]!!.contains(
268+
it.uuid.toString().lowercase(),
269+
)
270+
}.map { char ->
271+
hashMapOf(
272+
"id" to char.uuid.toString().lowercase(),
273+
)
274+
},
275+
)
276+
}
277+
connectedContinuation?.resume(Unit)
278+
connectedContinuation = null
279+
}
280+
241281
@Deprecated("Deprecated in Java")
242282
override fun onCharacteristicRead(
243283
gatt: BluetoothGatt,
@@ -255,37 +295,13 @@ class Peripheral(
255295
if (status != BluetoothGatt.GATT_SUCCESS) {
256296
Log.d(TAG, "error getting characteristic $charUUID: $status")
257297
} else {
258-
if (!discoveredCharacteristics.containsKey(characteristic.service.uuid.toString())) {
298+
if (!discoveredCharacteristics.containsKey(charSvcUUID)) {
259299
discoveredCharacteristics[charSvcUUID] = mutableSetOf()
260300
}
261301
discoveredCharacteristics[charSvcUUID]?.add(charUUID)
262302
}
263303
if (discoveredSvcDoneCount == neededCharDoneCount) {
264-
_discoveredServices =
265-
gatt.services
266-
.filter { discoveredCharacteristics.containsKey(it.uuid.toString().lowercase()) }
267-
.map { svc ->
268-
hashMapOf(
269-
"id" to svc.uuid.toString().lowercase(),
270-
"characteristics" to
271-
svc.characteristics
272-
.filter {
273-
discoveredCharacteristics[
274-
it.service.uuid
275-
.toString()
276-
.lowercase(),
277-
]!!.contains(
278-
it.uuid.toString().lowercase(),
279-
)
280-
}.map { char ->
281-
hashMapOf(
282-
"id" to char.uuid.toString().lowercase(),
283-
)
284-
},
285-
)
286-
}
287-
connectedContinuation?.resume(Unit)
288-
connectedContinuation = null
304+
serviceDiscoveryDone(gatt)
289305
}
290306
}
291307
}

android/src/main/kotlin/com/viam/ble/PeripheralManager.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ class PeripheralManager(
167167

168168
suspend fun startAdvertising(withName: String) {
169169
mustBePoweredOn()
170-
btMan.adapter.setName(withName)
170+
if (withName.isNotEmpty()) {
171+
btMan.adapter.setName(withName)
172+
}
171173
val settings =
172174
AdvertiseSettings
173175
.Builder()

ios/Classes/BlePlugin.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,19 @@ public class BlePlugin: NSObject, FlutterPlugin {
6666
case "peripheralManagerAddReadOnlyService":
6767
if let arguments = call.arguments as? [String: Any],
6868
let uuid = arguments[kUUIDParamName] as? String,
69-
let charDescs = arguments[kCharacteristicsParamName] as? [String: String] {
69+
var charDescs = arguments[kCharacteristicsParamName] as? [String: String] {
7070
var chars: [CBMutableCharacteristic] = []
7171
guard let svcTypeUUID = UUID(uuidString: uuid) else {
7272
throwInvalidArguments(call, result, "invalid service type UUID")
7373
return
7474
}
75-
for (charUUID, value) in charDescs {
75+
let sortedKeys = Array(charDescs.keys).sorted(by: <)
76+
for charUUID in sortedKeys {
7677
guard let charTypeUUID = UUID(uuidString: charUUID) else {
7778
throwInvalidArguments(call, result, "invalid characteristic type UUID")
7879
return
7980
}
81+
let value = charDescs[charUUID]!
8082
chars.append(CBMutableCharacteristic(
8183
type: CBUUID(nsuuid: charTypeUUID),
8284
properties: [.read],

ios/Classes/PeripheralManager.swift

+7-4
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,15 @@ public class PeripheralManager: NSObject {
119119
}
120120
}
121121

122-
public func startAdvertising(withName: String) throws {
122+
public func startAdvertising(withName: String?) throws {
123123
try mustBePoweredOn()
124+
var advertData: [String: Any] =
125+
[CBAdvertisementDataServiceUUIDsKey: servicesToAdvertise.map { $0.uuid }]
126+
if withName != nil {
127+
advertData[CBAdvertisementDataLocalNameKey] = withName
128+
}
124129
servicesToAdvertiseLock.withLock {
125-
manager.startAdvertising(
126-
[CBAdvertisementDataLocalNameKey: withName,
127-
CBAdvertisementDataServiceUUIDsKey: servicesToAdvertise.map { $0.uuid }])
130+
manager.startAdvertising(advertData)
128131
}
129132
}
130133

lib/src/ble.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class BlePeripheral {
6161
_BlePlatform.instance.peripheralManagerAddReadOnlyService(uuid, characteristicValues);
6262

6363
/// Starts advertising with the given local name along with any added services from [addReadOnlyService].
64-
Future<void> startAdvertising(String name) => _BlePlatform.instance.peripheralManagerStartAdvertising(name);
64+
Future<void> startAdvertising([String name = '']) => _BlePlatform.instance.peripheralManagerStartAdvertising(name);
6565

6666
/// Publishes an L2CAP channel and returns the corresponding PSM and stream to await new channels.
6767
///

0 commit comments

Comments
 (0)