Reputation: 4295
I have a custom BLE peripheral that advertises data like this:
In other words, my BLE peripheral advertises a service UUID associated with a unique identifier in advertised service data, but it does not add that service UUID to advertised service list because if I do that, I don't have room in the BLE frame to add battery level when I need to.
On iOS, I'm able to scan with a filter based on service UUID and see my peripheral. But on Android, with the following scan filter, I don't see my peripheral:
val scanSettingsBuilder = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setReportDelay(0L)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
scanSettingsBuilder
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
}
bluetoothAdapter?.bluetoothLeScanner?.startScan(
arrayListOf(ScanFilter.Builder().setServiceUuid(ParcelUuid(UUID.fromString("00004865-726f-6e54-7261-636b2d475053"))).build()),
scanSettingsBuilder.build(),
leScanCallback
)
Does anyone have more details about how the serviceUUID-based scan filter works, and what are the conditions a peripheral must meet in order to be accepted by the filter?
Upvotes: 8
Views: 5776
Reputation: 469
Step 1)
let kServiceUART = CBUUID(string: "0x1800")
var peripheralHeartRateMonitor: CBPeripheral?`
Step 2)
cbManger = CBCentralManager(delegate: self, queue: .main)
Step 3)
extension GuidedStartOnPhoneVC: CBCentralManagerDelegate, CBPeripheralDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unsupported:
print("BLe Unsupported")
break
case .unauthorized:
print("BLe unauthorized")
break
case .poweredOff:
let alertMessgesInst = AlertMessages.sharedInstance
CommonUtils.showAlert(alertMessgesInst.actofit_Title, message: alertMessgesInst.trun_On_blueTooth)
break
case .poweredOn:
if isNewFirmWareOFImpulse {
let uuidString = StorageServices.readFromDefaults(key: Constants.userDefaultKeys.impulseUUID)
let uuid = UUID(uuidString:uuidString as! String )
let device = cbManger.retrievePeripherals(withIdentifiers: [uuid!])
peripheralHeartRateMonitor = device.first
peripheralHeartRateMonitor!.delegate = self
cbManger?.connect(peripheralHeartRateMonitor!)
}else {
let option:[String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber(value: false)]
cbManger.scanForPeripherals(withServices: nil, options: option)
}
break
case .unknown:
print("BLe unknown")
break
default:
break
} // End Swith
} // End 'centralManagerDidUpdateState' function.
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if isNewFirmWareOFImpulse {
peripheralHeartRateMonitor = peripheral
print("UUid of band is :- \(peripheralHeartRateMonitor?.identifier.uuidString)")
if impulseName == peripheral.name {
peripheralHeartRateMonitor!.delegate = self
cbManger.stopScan()
// STEP 6: connect to the discovered peripheral of interest
cbManger?.connect(peripheralHeartRateMonitor!)
} // End impulse condition
}else {
let keysArray = advertisementData.keys
if let tempImpulseName = peripheral.name {
print(impulseName + " and " + tempImpulseName )
if impulseName == tempImpulseName {
for key in keysArray {
if key == "kCBAdvDataManufacturerData"{
let manufactureData = advertisementData[key]
if let stringValue = manufactureData.debugDescription as? String {
var heartValue: String = String()
heartValue = stringValue
heartValue.removeLast()
heartValue.removeLast()
let last = heartValue.removeLast()
let secondLast = heartValue.removeLast()
let hR = String([secondLast, last])
if let value = UInt8(hR, radix: 16){
if Int(value) > 60 {
hrArray.append(Int(value))
}
} // End the value block
} // end of if 'stringValue' condition
} // end 'Key' if condition
} // End for each loop
} // End impulse condition
} // End pheripheral if condition
} // end version condition
} // End function 'didDiscover peripheral'.
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
// STEP 8: look for services of interest on peripheral
peripheralHeartRateMonitor?.discoverServices(nil)
} // END func centralManager(... didConnect peripheral
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if error != nil {
print("didDiscoverService Error :- \(error!)")
}
for service in peripheral.services! {
print("Service: \(service)")
if service.uuid.uuidString == "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" {
print("Service: \(service)")
// STEP 9: look for characteristics of interest
// within services of interest
peripheral.discoverCharacteristics(nil, for: service)
}
}
} // END func peripheral(... didDiscoverServices
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for characteristic in service.characteristics! {
print(characteristic)
if characteristic.uuid.uuidString == "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" {
peripheral.setNotifyValue(true, for: characteristic)
}
if characteristic.uuid.uuidString == "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" {
peripheral.setNotifyValue(true, for: characteristic)
}
//
} // END for
} // END func peripheral(... didDiscoverCharacteristicsFor service
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
// print(characteristic.value!.hexString)
if !isOnRestScreen{
if let batteryLevelValue = characteristic.value {
var buffer = [UInt8](batteryLevelValue)
// print(buffer)
print("Array count is \(buffer.count) :- \(buffer)")
if buffer.count == 20 {
buffer.removeFirst(4)
//MARK:- get Frame Array from the Impulse.
let array1 = Array(buffer.prefix(upTo: 8))
// let array2 = Array(buffer.suffix(from: 8))
makeProceedArray(tempArray: array1)
// makeProceedArray(tempArray: array2)
}else {
print("\(characteristic.service)")
}
}
}
} // END if characteristic.uuid
func decodePeripheralState(peripheralState: CBPeripheralState) {
switch peripheralState {
case .disconnected:
print("Peripheral state: disconnected")
case .connected:
print("Peripheral state: connected")
case .connecting:
print("Peripheral state: connecting")
case .disconnecting:
print("Peripheral state: disconnecting")
}
} // END func decodePeripheralState(peripheralState
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
// print("Disconnected!")
if error != nil {
print("didDisconnectPeripheral Error :- \(error!)")
}
// STEP 16: in this use-case, start scanning
// for the same peripheral or another, as long
// as they're HRMs, to come back online
cbManger?.scanForPeripherals(withServices: [kServiceUART])
} // END func centralManager(... didDisconnectPeripheral peripheral
} // End extension
Upvotes: -2
Reputation: 4295
I figured out how to make it work... sort of. The problem is that my filter was on serviceUuid
, which I assume looks at peripherals that advertise the UUID in the advertisedServices
collection. My peripheral only advertises the UUID as a key in its serviceData
associative array, so I switched to the serviceData
filter as follows, and now I can find my peripheral:
AsyncTask.execute {
val scanFilters = Settings.scannedBleServices.values.map {
ScanFilter.Builder().setServiceData(it, null).build()
}
val scanSettingsBuilder = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setReportDelay(0L)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
scanSettingsBuilder
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
}
bluetoothAdapter?.bluetoothLeScanner?.startScan(
scanFilters,
scanSettingsBuilder.build(),
leScanCallback
)
}
The problem is that now the filter is too permissive, as I get a callback for every peripheral around, even those without any serviceData
, just as if I had specified no filter at all. Maybe it's because I passed null
as a second parameter to setServiceData
in the filter because I didn't know what else to add there. And the documentation is not exactly helpful.
My guess is that it's enough for the scan to work in the background (I haven't tried yet), but it would make more sense if I could restrict the number of times the callback is called and I didn't have to filter by myself.
Upvotes: 5