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