Add manufacturer data filtering (#23)

* Add manufacturer data filtering

* Implement manufacturer data filter in windows

* Fix manufacturerData filtering on windows

* Fix linux

* Clean comments

---------

Co-authored-by: Foti Dim <fdimanidis@gmail.com>
This commit is contained in:
Rohit Sangwan
2024-03-18 23:14:44 +05:30
committed by GitHub
parent 580fca6e4a
commit 1a0e48ba1a
22 changed files with 770 additions and 356 deletions
+40 -3
View File
@@ -129,20 +129,52 @@ struct UniversalBleCharacteristic {
}
}
/// Scan Filters
///
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalScanFilter {
var withServices: [String?]
var withManufacturerData: [UniversalManufacturerDataFilter?]
static func fromList(_ list: [Any?]) -> UniversalScanFilter? {
let withServices = list[0] as! [String?]
let withManufacturerData = list[1] as! [UniversalManufacturerDataFilter?]
return UniversalScanFilter(
withServices: withServices
withServices: withServices,
withManufacturerData: withManufacturerData
)
}
func toList() -> [Any?] {
return [
withServices
withServices,
withManufacturerData,
]
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct UniversalManufacturerDataFilter {
var companyIdentifier: Int64? = nil
var data: FlutterStandardTypedData? = nil
var mask: FlutterStandardTypedData? = nil
static func fromList(_ list: [Any?]) -> UniversalManufacturerDataFilter? {
let companyIdentifier: Int64? = isNullish(list[0]) ? nil : (list[0] is Int64? ? list[0] as! Int64? : Int64(list[0] as! Int32))
let data: FlutterStandardTypedData? = nilOrValue(list[1])
let mask: FlutterStandardTypedData? = nilOrValue(list[2])
return UniversalManufacturerDataFilter(
companyIdentifier: companyIdentifier,
data: data,
mask: mask
)
}
func toList() -> [Any?] {
return [
companyIdentifier,
data,
mask,
]
}
}
@@ -157,6 +189,8 @@ private class UniversalBlePlatformChannelCodecReader: FlutterStandardReader {
case 130:
return UniversalBleService.fromList(self.readValue() as! [Any?])
case 131:
return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?])
case 132:
return UniversalScanFilter.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
@@ -175,9 +209,12 @@ private class UniversalBlePlatformChannelCodecWriter: FlutterStandardWriter {
} else if let value = value as? UniversalBleService {
super.writeByte(130)
super.writeValue(value.toList())
} else if let value = value as? UniversalScanFilter {
} else if let value = value as? UniversalManufacturerDataFilter {
super.writeByte(131)
super.writeValue(value.toList())
} else if let value = value as? UniversalScanFilter {
super.writeByte(132)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
+45 -2
View File
@@ -143,11 +143,11 @@ public extension CBPeripheral {
extension String {
var validFullUUID: String {
let uuidLength = self.count
let uuidLength = count
if uuidLength == 4 || uuidLength == 8 {
let baseUuid = "00000000-0000-1000-8000-00805F9B34FB"
let start = baseUuid.startIndex
let range = baseUuid.index(start, offsetBy: 4-uuidLength)..<baseUuid.index(start, offsetBy: 4)
let range = baseUuid.index(start, offsetBy: 4 - uuidLength) ..< baseUuid.index(start, offsetBy: 4)
return baseUuid.replacingCharacters(in: range, with: self)
} else {
return self
@@ -155,6 +155,49 @@ extension String {
}
}
extension FlutterStandardTypedData {
func toData() -> Data {
return Data(data)
}
}
func isManufacturerDataMatchingFilters(filters: [UniversalManufacturerDataFilter], msd: Data?) -> Bool {
guard let msd = msd, !msd.isEmpty else {
return false
}
for filter in filters {
guard let companyIdentifier: Int64 = filter.companyIdentifier else {
continue
}
let manufacturerId = msd.subdata(in: 0 ..< 2).withUnsafeBytes { $0.load(as: UInt16.self) }
let manufacturerData = msd.subdata(in: 2 ..< msd.count)
if manufacturerId == companyIdentifier && findData(find: filter.data?.toData(), inData: manufacturerData, usingMask: filter.mask?.toData()) {
return true
}
}
return false
}
func findData(find: Data?, inData data: Data, usingMask mask: Data?) -> Bool {
if let find = find {
// If mask is null, use a default mask of all 1s
let mask = mask ?? Data(repeating: 0xFF, count: find.count)
// Ensure find & mask are same length
guard find.count == mask.count else {
return false
}
for i in 0 ..< find.count {
// Perform bitwise AND with mask and then compare
if (find[i] & mask[i]) != (data[i] & mask[i]) {
return false
}
}
}
return true
}
// Future classes
class CharacteristicReadFuture {
let deviceId: String
+15 -2
View File
@@ -27,6 +27,7 @@ private var discoveredPeripherals = [String: CBPeripheral]()
private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentralManagerDelegate, CBPeripheralDelegate {
var callbackChannel: UniversalBleCallbackChannel
private lazy var manager: CBCentralManager = .init(delegate: self, queue: nil)
private var scanFilter: UniversalScanFilter? = nil
private var discoveredServicesProgressMap: [String: [UniversalBleService]] = [:]
private var characteristicReadFutures = [CharacteristicReadFuture]()
private var characteristicWriteFutures = [CharacteristicWriteFuture]()
@@ -57,18 +58,20 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
}
func startScan(filter: UniversalScanFilter?) throws {
// Apply services filter
var withServices: [CBUUID] = []
for service in filter?.withServices ?? [] {
if let service = service {
if UUID(uuidString: service.validFullUUID) == nil {
if UUID(uuidString: service.validFullUUID) == nil {
throw FlutterError(code: "IllegalArgument", message: "Invalid service UUID:\(service)", details: nil)
}
withServices.append(CBUUID(string: service))
}
}
// Save scanFilter for later user
scanFilter = filter
manager.scanForPeripherals(withServices: withServices)
}
@@ -241,6 +244,16 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
discoveredPeripherals[peripheral.uuid.uuidString] = peripheral
let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
let services = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
// Handle ScanFilters
if let filter = scanFilter {
let manufacturerFilter = filter.withManufacturerData.compactMap { $0 }
// If scan filters are not empty, check if manufacturer data matches filters
if !manufacturerFilter.isEmpty, !isManufacturerDataMatchingFilters(filters: manufacturerFilter, msd: manufacturerData) {
return
}
}
callbackChannel.onScanResult(result: UniversalBleScanResult(
deviceId: peripheral.uuid.uuidString,
name: peripheral.name,