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:
Vendored
+1
@@ -21,6 +21,7 @@
|
||||
"pairable",
|
||||
"rssi",
|
||||
"simpleble",
|
||||
"subdata",
|
||||
"sublist",
|
||||
"unawaited",
|
||||
"UNIVERSALBLE",
|
||||
|
||||
@@ -127,21 +127,53 @@ data class UniversalBleCharacteristic (
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated class from Pigeon that represents data sent in messages. */
|
||||
/**
|
||||
* Scan Filters
|
||||
*
|
||||
* Generated class from Pigeon that represents data sent in messages.
|
||||
*/
|
||||
data class UniversalScanFilter (
|
||||
val withServices: List<String?>
|
||||
val withServices: List<String?>,
|
||||
val withManufacturerData: List<UniversalManufacturerDataFilter?>
|
||||
|
||||
) {
|
||||
companion object {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun fromList(list: List<Any?>): UniversalScanFilter {
|
||||
val withServices = list[0] as List<String?>
|
||||
return UniversalScanFilter(withServices)
|
||||
val withManufacturerData = list[1] as List<UniversalManufacturerDataFilter?>
|
||||
return UniversalScanFilter(withServices, withManufacturerData)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
return listOf<Any?>(
|
||||
withServices,
|
||||
withManufacturerData,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated class from Pigeon that represents data sent in messages. */
|
||||
data class UniversalManufacturerDataFilter (
|
||||
val companyIdentifier: Long? = null,
|
||||
val data: ByteArray? = null,
|
||||
val mask: ByteArray? = null
|
||||
|
||||
) {
|
||||
companion object {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun fromList(list: List<Any?>): UniversalManufacturerDataFilter {
|
||||
val companyIdentifier = list[0].let { if (it is Int) it.toLong() else it as Long? }
|
||||
val data = list[1] as ByteArray?
|
||||
val mask = list[2] as ByteArray?
|
||||
return UniversalManufacturerDataFilter(companyIdentifier, data, mask)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
return listOf<Any?>(
|
||||
companyIdentifier,
|
||||
data,
|
||||
mask,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -166,6 +198,11 @@ private object UniversalBlePlatformChannelCodec : StandardMessageCodec() {
|
||||
}
|
||||
}
|
||||
131.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
UniversalManufacturerDataFilter.fromList(it)
|
||||
}
|
||||
}
|
||||
132.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
UniversalScanFilter.fromList(it)
|
||||
}
|
||||
@@ -187,10 +224,14 @@ private object UniversalBlePlatformChannelCodec : StandardMessageCodec() {
|
||||
stream.write(130)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is UniversalScanFilter -> {
|
||||
is UniversalManufacturerDataFilter -> {
|
||||
stream.write(131)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is UniversalScanFilter -> {
|
||||
stream.write(132)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
else -> super.writeValue(stream, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ fun Int.parseGattErrorCode(): String? {
|
||||
}
|
||||
|
||||
fun UniversalScanFilter.toScanFilters(): List<ScanFilter> {
|
||||
var scanFilters: List<ScanFilter> = emptyList()
|
||||
var scanFilters: ArrayList<ScanFilter> = arrayListOf()
|
||||
|
||||
// Add withServices Filter
|
||||
for (service in this.withServices) {
|
||||
@@ -152,7 +152,7 @@ fun UniversalScanFilter.toScanFilters(): List<ScanFilter> {
|
||||
val serviceUUID = service?.validFullUUID()
|
||||
serviceUUID?.let {
|
||||
val parcelUUId = ParcelUuid.fromString(it)
|
||||
scanFilters = scanFilters.plus(
|
||||
scanFilters.add(
|
||||
ScanFilter.Builder().setServiceUuid(parcelUUId).build()
|
||||
)
|
||||
Log.e(TAG, "scanFilters: $parcelUUId")
|
||||
@@ -167,7 +167,37 @@ fun UniversalScanFilter.toScanFilters(): List<ScanFilter> {
|
||||
}
|
||||
}
|
||||
|
||||
return scanFilters
|
||||
// Add ManufacturerData Filter
|
||||
for (manufacturerData in this.withManufacturerData) {
|
||||
try {
|
||||
manufacturerData?.companyIdentifier?.let {
|
||||
val data: ByteArray = manufacturerData.data ?: ByteArray(0)
|
||||
val mask: ByteArray? = manufacturerData.mask
|
||||
if (mask == null) {
|
||||
scanFilters.add(
|
||||
ScanFilter.Builder().setManufacturerData(
|
||||
it.toInt(), data
|
||||
).build()
|
||||
)
|
||||
} else {
|
||||
scanFilters.add(
|
||||
ScanFilter.Builder().setManufacturerData(
|
||||
it.toInt(), data, mask
|
||||
).build()
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, e.toString())
|
||||
throw FlutterError(
|
||||
"illegalIllegalArgument",
|
||||
"Invalid manufacturerData: ${manufacturerData?.companyIdentifier} ${manufacturerData?.data} ${manufacturerData?.mask}",
|
||||
e.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return scanFilters.toList()
|
||||
}
|
||||
|
||||
val ScanResult.manufacturerDataHead: ByteArray?
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -24,7 +24,6 @@ class MockUniversalBle extends UniversalBlePlatform {
|
||||
|
||||
@override
|
||||
Future<void> startScan({
|
||||
WebRequestOptionsBuilder? webRequestOptions,
|
||||
ScanFilter? scanFilter,
|
||||
}) async {
|
||||
onScanResult?.call(_mockBleScanResult);
|
||||
|
||||
+24
-12
@@ -25,9 +25,9 @@ class _MyAppState extends State<MyApp> {
|
||||
bool _isQueueEnabled = true;
|
||||
|
||||
AvailabilityState? bleAvailabilityState;
|
||||
late WebRequestOptionsBuilder _requestOptions;
|
||||
final List<String> _services = [
|
||||
"00001800-0000-1000-8000-00805f9b34fb",
|
||||
"0000180f-0000-1000-8000-00805f9b34fb",
|
||||
"00002a00-0000-1000-8000-00805f9b34fb",
|
||||
"00002a01-0000-1000-8000-00805f9b34fb",
|
||||
"00002a19-0000-1000-8000-00805f9b34fb",
|
||||
@@ -48,13 +48,6 @@ class _MyAppState extends State<MyApp> {
|
||||
UniversalBle.queuesCommands = _isQueueEnabled;
|
||||
UniversalBle.timeout = const Duration(seconds: 10);
|
||||
|
||||
/// Add common services for web
|
||||
if (kIsWeb) {
|
||||
_services.addAll(WebRequestOptionsBuilder.defaultServices);
|
||||
}
|
||||
_requestOptions =
|
||||
WebRequestOptionsBuilder.acceptAllDevices(optionalServices: _services);
|
||||
|
||||
UniversalBle.onAvailabilityChange = (state) {
|
||||
setState(() {
|
||||
bleAvailabilityState = state;
|
||||
@@ -77,6 +70,28 @@ class _MyAppState extends State<MyApp> {
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> startScan() async {
|
||||
await UniversalBle.startScan(
|
||||
scanFilter: ScanFilter(
|
||||
withServices: kIsWeb ? _services : [],
|
||||
withManufacturerData: [
|
||||
ManufacturerDataFilter(
|
||||
companyIdentifier: 0x012D,
|
||||
data: Uint8List.fromList(
|
||||
[0x03, 0x00, 0x64, 0x00],
|
||||
),
|
||||
),
|
||||
ManufacturerDataFilter(
|
||||
companyIdentifier: 0x012D,
|
||||
data: Uint8List.fromList(
|
||||
[0x03, 0x00, 0x65, 0x00],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -110,10 +125,7 @@ class _MyAppState extends State<MyApp> {
|
||||
_isScanning = true;
|
||||
});
|
||||
try {
|
||||
await UniversalBle.startScan(
|
||||
webRequestOptions: _requestOptions,
|
||||
// scanFilter: ScanFilter(withServices: ['180f']),
|
||||
);
|
||||
await startScan();
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isScanning = false;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export 'package:universal_ble/src/models/uuid.dart';
|
||||
export 'package:universal_ble/src/models/scan_filter.dart';
|
||||
export 'package:universal_ble/src/models/web_request_options_builder.dart';
|
||||
export 'package:universal_ble/src/models/ble_property.dart';
|
||||
export 'package:universal_ble/src/models/ble_service.dart';
|
||||
export 'package:universal_ble/src/models/availability_state.dart';
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class ScanFilter {
|
||||
final List<String> withServices;
|
||||
final List<ManufacturerDataFilter> withManufacturerData;
|
||||
|
||||
ScanFilter({
|
||||
this.withServices = const [],
|
||||
this.withManufacturerData = const [],
|
||||
});
|
||||
}
|
||||
|
||||
class ManufacturerDataFilter {
|
||||
int? companyIdentifier;
|
||||
|
||||
// Mask and data must be of same length
|
||||
Uint8List? data;
|
||||
|
||||
/// For any bit in the mask, set it the 1 if it needs to match
|
||||
/// the one in manufacturer data, otherwise set it to 0.
|
||||
/// The 'mask' must have the same length as 'data'.
|
||||
Uint8List? mask;
|
||||
|
||||
ManufacturerDataFilter({
|
||||
this.companyIdentifier,
|
||||
this.data,
|
||||
this.mask,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter_web_bluetooth/flutter_web_bluetooth.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
class WebRequestOptionsBuilder {
|
||||
final bool _acceptAllDevices;
|
||||
final List<WebRequestFilterBuilder> _requestFilters;
|
||||
final List<WebRequestFilterBuilder>? _exclusionFilters;
|
||||
List<String>? _optionalServices;
|
||||
|
||||
WebRequestOptionsBuilder(
|
||||
List<WebRequestFilterBuilder> requestFilters, {
|
||||
List<WebRequestFilterBuilder>? exclusionFilters,
|
||||
List<String>? optionalServices,
|
||||
}) : _optionalServices = optionalServices,
|
||||
_requestFilters = requestFilters,
|
||||
_exclusionFilters = exclusionFilters,
|
||||
_acceptAllDevices = false {
|
||||
if (_requestFilters.isEmpty) {
|
||||
throw StateError(
|
||||
'No filters have been set, consider using '
|
||||
'RequestOptionsBuilder.acceptAllDevices() instead.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// To accept all devices
|
||||
WebRequestOptionsBuilder.acceptAllDevices({
|
||||
List<String>? optionalServices,
|
||||
}) : _optionalServices = optionalServices,
|
||||
_acceptAllDevices = true,
|
||||
_requestFilters = [],
|
||||
_exclusionFilters = null;
|
||||
|
||||
/// For internal use
|
||||
/// To convert UniversalBleRequestOptions to FlutterWebBluetoothRequestOptions
|
||||
RequestOptionsBuilder toRequestOptionsBuilder({ScanFilter? scanFilter}) {
|
||||
if (scanFilter != null) _applyScanFilter(scanFilter);
|
||||
|
||||
if (_acceptAllDevices) {
|
||||
return RequestOptionsBuilder.acceptAllDevices(
|
||||
optionalServices: _optionalServices,
|
||||
);
|
||||
}
|
||||
|
||||
return RequestOptionsBuilder(
|
||||
_requestFilters.map((e) => e.getRequestFilterBuilder()).toList(),
|
||||
exclusionFilters:
|
||||
_exclusionFilters?.map((e) => e.getRequestFilterBuilder()).toList(),
|
||||
optionalServices: _optionalServices,
|
||||
);
|
||||
}
|
||||
|
||||
/// To apply scan filter
|
||||
void _applyScanFilter(ScanFilter scanFilter) {
|
||||
List<String> filterServices = scanFilter.withServices;
|
||||
_optionalServices ??= filterServices.toValidUUIDList();
|
||||
|
||||
for (var requestFilter in _requestFilters) {
|
||||
// Apply services filter
|
||||
requestFilter.services ??= filterServices.toValidUUIDList();
|
||||
}
|
||||
}
|
||||
|
||||
/// List of common services
|
||||
static List<String> get defaultServices {
|
||||
return BluetoothDefaultServiceUUIDS.values
|
||||
.map((e) => e.uuid.toString())
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// List of common services in 16 bit
|
||||
static List<String> get defaultServices16Bit {
|
||||
return BluetoothDefaultServiceUUIDS.values
|
||||
.map((e) => e.uuid16.toString())
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
/// To filter by name, namePrefix, services, manufacturerData
|
||||
class WebRequestFilterBuilder {
|
||||
String? name;
|
||||
String? namePrefix;
|
||||
List<String>? services;
|
||||
List<WebManufacturerDataFilterBuilder>? manufacturerData;
|
||||
|
||||
WebRequestFilterBuilder({
|
||||
this.name,
|
||||
this.namePrefix,
|
||||
this.services,
|
||||
this.manufacturerData,
|
||||
});
|
||||
|
||||
RequestFilterBuilder getRequestFilterBuilder() {
|
||||
return RequestFilterBuilder(
|
||||
name: name,
|
||||
namePrefix: namePrefix,
|
||||
services: services,
|
||||
manufacturerData: manufacturerData
|
||||
?.map((e) => e.getManufacturerDataFilterBuilder())
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// To filter by manufacturer data
|
||||
class WebManufacturerDataFilterBuilder {
|
||||
int? companyIdentifier;
|
||||
Uint8List? dataPrefix;
|
||||
Uint8List? mask;
|
||||
WebManufacturerDataFilterBuilder({
|
||||
this.companyIdentifier,
|
||||
this.dataPrefix,
|
||||
this.mask,
|
||||
});
|
||||
|
||||
ManufacturerDataFilterBuilder getManufacturerDataFilterBuilder() {
|
||||
return ManufacturerDataFilterBuilder(
|
||||
companyIdentifier: companyIdentifier,
|
||||
dataPrefix: dataPrefix,
|
||||
mask: mask,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -44,14 +44,10 @@ class UniversalBle {
|
||||
/// might throw errors if Bluetooth is not available
|
||||
/// `webRequestOptions` supported on Web only
|
||||
static Future<void> startScan({
|
||||
WebRequestOptionsBuilder? webRequestOptions,
|
||||
ScanFilter? scanFilter,
|
||||
}) async {
|
||||
return await _executeCommand(
|
||||
() => _platform.startScan(
|
||||
webRequestOptions: webRequestOptions,
|
||||
scanFilter: scanFilter,
|
||||
),
|
||||
() => _platform.startScan(scanFilter: scanFilter),
|
||||
timeout: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
final BlueZClient _client = BlueZClient();
|
||||
|
||||
BlueZAdapter? _activeAdapter;
|
||||
ScanFilter? _scanFilter;
|
||||
Completer<void>? _initializationCompleter;
|
||||
final Map<String, BlueZDevice> _devices = {};
|
||||
final Map<String, StreamSubscription> _deviceStreamSubscriptions = {};
|
||||
@@ -54,7 +55,6 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
|
||||
@override
|
||||
Future<void> startScan({
|
||||
WebRequestOptionsBuilder? webRequestOptions,
|
||||
ScanFilter? scanFilter,
|
||||
}) async {
|
||||
await _ensureInitialized();
|
||||
@@ -63,6 +63,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
_activeAdapter?.setDiscoveryFilter(
|
||||
uuids: scanFilter?.withServices.toValidUUIDList(),
|
||||
);
|
||||
_scanFilter = scanFilter;
|
||||
await _activeAdapter?.startDiscovery();
|
||||
_client.devices.forEach(_onDeviceAdd);
|
||||
}
|
||||
@@ -371,6 +372,16 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
}
|
||||
|
||||
void _onDeviceAdd(BlueZDevice device) {
|
||||
if (!_isValidDevice(device)) return;
|
||||
|
||||
// Apply Filters
|
||||
if (_scanFilter != null) {
|
||||
List<ManufacturerDataFilter>? manufacturerDataFilter =
|
||||
_scanFilter?.withManufacturerData;
|
||||
if (manufacturerDataFilter != null &&
|
||||
manufacturerDataFilter.isNotEmpty) {}
|
||||
}
|
||||
|
||||
// Update scan results only if rssi is available
|
||||
if (device.rssi != 0) onScanResult?.call(device.toBleScanResult());
|
||||
|
||||
@@ -421,6 +432,79 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
});
|
||||
}
|
||||
|
||||
bool _isValidDevice(BlueZDevice device) {
|
||||
ScanFilter? scanFilter = _scanFilter;
|
||||
if (scanFilter == null) return true;
|
||||
|
||||
// Check manufacturerData filter
|
||||
if (!_isValidManufacturerData(
|
||||
scanFilter.withManufacturerData,
|
||||
device.manufacturerDataFilter,
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _isValidManufacturerData(
|
||||
List<ManufacturerDataFilter> filterMfdList,
|
||||
List<ManufacturerDataFilter> deviceMfdList,
|
||||
) {
|
||||
if (filterMfdList.isEmpty) return true;
|
||||
if (deviceMfdList.isEmpty) return false;
|
||||
|
||||
// Check all filters
|
||||
for (var filterMfd in filterMfdList) {
|
||||
// Check if device have manufacturerData for this filter
|
||||
for (var deviceMfd in deviceMfdList) {
|
||||
// Check companyIdentifier
|
||||
if (filterMfd.companyIdentifier != deviceMfd.companyIdentifier) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check data
|
||||
Uint8List? filterData = filterMfd.data;
|
||||
Uint8List? deviceData = deviceMfd.data;
|
||||
|
||||
// If filter data is null and device data is not, continue to next deviceMfd
|
||||
if (filterData != null && deviceData == null) continue;
|
||||
|
||||
if (filterData == null || deviceData == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (filterData.length > deviceData.length) continue;
|
||||
|
||||
// Apply mask
|
||||
Uint8List? filterMask = filterMfd.mask;
|
||||
bool dataMatched = true;
|
||||
if (filterMask != null && (filterMask.length == filterData.length)) {
|
||||
for (int i = 0; i < filterData.length; i++) {
|
||||
if ((filterData[i] & filterMask[i]) !=
|
||||
(deviceData[i] & filterMask[i])) {
|
||||
dataMatched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Compare data directly
|
||||
else {
|
||||
for (int i = 0; i < filterData.length; i++) {
|
||||
if (filterData[i] != deviceData[i]) {
|
||||
dataMatched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dataMatched) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void _onDeviceRemoved(BlueZDevice device) {
|
||||
_devices.remove(device.address);
|
||||
// Stop listener
|
||||
@@ -451,6 +535,24 @@ class BluezProperty {
|
||||
}
|
||||
|
||||
extension BlueZDeviceExtension on BlueZDevice {
|
||||
List<ManufacturerDataFilter> get manufacturerDataFilter {
|
||||
try {
|
||||
if (manufacturerData.isEmpty) return [];
|
||||
return manufacturerData.entries
|
||||
.map((e) => ManufacturerDataFilter(
|
||||
companyIdentifier: e.key.id,
|
||||
data: Uint8List.fromList(e.value),
|
||||
))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
UniversalBlePlatform.logInfo(
|
||||
'Error parsing manufacturerData: $e',
|
||||
isError: true,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List get manufacturerDataHead {
|
||||
try {
|
||||
if (manufacturerData.isEmpty) return Uint8List(0);
|
||||
|
||||
@@ -15,8 +15,7 @@ PlatformException _createConnectionError(String channelName) {
|
||||
);
|
||||
}
|
||||
|
||||
List<Object?> wrapResponse(
|
||||
{Object? result, PlatformException? error, bool empty = false}) {
|
||||
List<Object?> wrapResponse({Object? result, PlatformException? error, bool empty = false}) {
|
||||
if (empty) {
|
||||
return <Object?>[];
|
||||
}
|
||||
@@ -98,8 +97,7 @@ class UniversalBleService {
|
||||
result as List<Object?>;
|
||||
return UniversalBleService(
|
||||
uuid: result[0]! as String,
|
||||
characteristics:
|
||||
(result[1] as List<Object?>?)?.cast<UniversalBleCharacteristic?>(),
|
||||
characteristics: (result[1] as List<Object?>?)?.cast<UniversalBleCharacteristic?>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -130,16 +128,21 @@ class UniversalBleCharacteristic {
|
||||
}
|
||||
}
|
||||
|
||||
/// Scan Filters
|
||||
class UniversalScanFilter {
|
||||
UniversalScanFilter({
|
||||
required this.withServices,
|
||||
required this.withManufacturerData,
|
||||
});
|
||||
|
||||
List<String?> withServices;
|
||||
|
||||
List<UniversalManufacturerDataFilter?> withManufacturerData;
|
||||
|
||||
Object encode() {
|
||||
return <Object?>[
|
||||
withServices,
|
||||
withManufacturerData,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -147,6 +150,38 @@ class UniversalScanFilter {
|
||||
result as List<Object?>;
|
||||
return UniversalScanFilter(
|
||||
withServices: (result[0] as List<Object?>?)!.cast<String?>(),
|
||||
withManufacturerData: (result[1] as List<Object?>?)!.cast<UniversalManufacturerDataFilter?>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UniversalManufacturerDataFilter {
|
||||
UniversalManufacturerDataFilter({
|
||||
this.companyIdentifier,
|
||||
this.data,
|
||||
this.mask,
|
||||
});
|
||||
|
||||
int? companyIdentifier;
|
||||
|
||||
Uint8List? data;
|
||||
|
||||
Uint8List? mask;
|
||||
|
||||
Object encode() {
|
||||
return <Object?>[
|
||||
companyIdentifier,
|
||||
data,
|
||||
mask,
|
||||
];
|
||||
}
|
||||
|
||||
static UniversalManufacturerDataFilter decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return UniversalManufacturerDataFilter(
|
||||
companyIdentifier: result[0] as int?,
|
||||
data: result[1] as Uint8List?,
|
||||
mask: result[2] as Uint8List?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -164,9 +199,12 @@ class _UniversalBlePlatformChannelCodec extends StandardMessageCodec {
|
||||
} else if (value is UniversalBleService) {
|
||||
buffer.putUint8(130);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is UniversalScanFilter) {
|
||||
} else if (value is UniversalManufacturerDataFilter) {
|
||||
buffer.putUint8(131);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is UniversalScanFilter) {
|
||||
buffer.putUint8(132);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
@@ -175,13 +213,15 @@ class _UniversalBlePlatformChannelCodec extends StandardMessageCodec {
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 128:
|
||||
case 128:
|
||||
return UniversalBleCharacteristic.decode(readValue(buffer)!);
|
||||
case 129:
|
||||
case 129:
|
||||
return UniversalBleScanResult.decode(readValue(buffer)!);
|
||||
case 130:
|
||||
case 130:
|
||||
return UniversalBleService.decode(readValue(buffer)!);
|
||||
case 131:
|
||||
case 131:
|
||||
return UniversalManufacturerDataFilter.decode(readValue(buffer)!);
|
||||
case 132:
|
||||
return UniversalScanFilter.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
@@ -198,14 +238,11 @@ class UniversalBlePlatformChannel {
|
||||
: __pigeon_binaryMessenger = binaryMessenger;
|
||||
final BinaryMessenger? __pigeon_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec =
|
||||
_UniversalBlePlatformChannelCodec();
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _UniversalBlePlatformChannelCodec();
|
||||
|
||||
Future<int> getBluetoothAvailabilityState() async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -231,10 +268,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<bool> enableBluetooth() async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -260,10 +295,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> startScan(UniversalScanFilter? filter) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -284,10 +317,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> stopScan() async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -308,10 +339,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> connect(String deviceId) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -332,10 +361,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> disconnect(String deviceId) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -355,19 +382,15 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setNotifiable(String deviceId, String service,
|
||||
String characteristic, int bleInputProperty) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
Future<void> setNotifiable(String deviceId, String service, String characteristic, int bleInputProperty) async {
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
);
|
||||
final List<Object?>? __pigeon_replyList = await __pigeon_channel.send(
|
||||
<Object?>[deviceId, service, characteristic, bleInputProperty])
|
||||
as List<Object?>?;
|
||||
final List<Object?>? __pigeon_replyList =
|
||||
await __pigeon_channel.send(<Object?>[deviceId, service, characteristic, bleInputProperty]) as List<Object?>?;
|
||||
if (__pigeon_replyList == null) {
|
||||
throw _createConnectionError(__pigeon_channelName);
|
||||
} else if (__pigeon_replyList.length > 1) {
|
||||
@@ -382,10 +405,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<List<UniversalBleService?>> discoverServices(String deviceId) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -406,23 +427,19 @@ class UniversalBlePlatformChannel {
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (__pigeon_replyList[0] as List<Object?>?)!
|
||||
.cast<UniversalBleService?>();
|
||||
return (__pigeon_replyList[0] as List<Object?>?)!.cast<UniversalBleService?>();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List> readValue(
|
||||
String deviceId, String service, String characteristic) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
Future<Uint8List> readValue(String deviceId, String service, String characteristic) async {
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
);
|
||||
final List<Object?>? __pigeon_replyList = await __pigeon_channel
|
||||
.send(<Object?>[deviceId, service, characteristic]) as List<Object?>?;
|
||||
final List<Object?>? __pigeon_replyList =
|
||||
await __pigeon_channel.send(<Object?>[deviceId, service, characteristic]) as List<Object?>?;
|
||||
if (__pigeon_replyList == null) {
|
||||
throw _createConnectionError(__pigeon_channelName);
|
||||
} else if (__pigeon_replyList.length > 1) {
|
||||
@@ -442,16 +459,14 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<int> requestMtu(String deviceId, int expectedMtu) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
);
|
||||
final List<Object?>? __pigeon_replyList = await __pigeon_channel
|
||||
.send(<Object?>[deviceId, expectedMtu]) as List<Object?>?;
|
||||
final List<Object?>? __pigeon_replyList =
|
||||
await __pigeon_channel.send(<Object?>[deviceId, expectedMtu]) as List<Object?>?;
|
||||
if (__pigeon_replyList == null) {
|
||||
throw _createConnectionError(__pigeon_channelName);
|
||||
} else if (__pigeon_replyList.length > 1) {
|
||||
@@ -470,24 +485,15 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> writeValue(String deviceId, String service,
|
||||
String characteristic, Uint8List value, int bleOutputProperty) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
Future<void> writeValue(String deviceId, String service, String characteristic, Uint8List value, int bleOutputProperty) async {
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
);
|
||||
final List<Object?>? __pigeon_replyList = await __pigeon_channel
|
||||
.send(<Object?>[
|
||||
deviceId,
|
||||
service,
|
||||
characteristic,
|
||||
value,
|
||||
bleOutputProperty
|
||||
]) as List<Object?>?;
|
||||
final List<Object?>? __pigeon_replyList =
|
||||
await __pigeon_channel.send(<Object?>[deviceId, service, characteristic, value, bleOutputProperty]) as List<Object?>?;
|
||||
if (__pigeon_replyList == null) {
|
||||
throw _createConnectionError(__pigeon_channelName);
|
||||
} else if (__pigeon_replyList.length > 1) {
|
||||
@@ -502,10 +508,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<bool> isPaired(String deviceId) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -531,10 +535,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> pair(String deviceId) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -555,10 +557,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> unPair(String deviceId) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -578,12 +578,9 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<UniversalBleScanResult?>> getConnectedDevices(
|
||||
List<String?> withServices) async {
|
||||
const String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectedDevices';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
Future<List<UniversalBleScanResult?>> getConnectedDevices(List<String?> withServices) async {
|
||||
const String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectedDevices';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -604,8 +601,7 @@ class UniversalBlePlatformChannel {
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (__pigeon_replyList[0] as List<Object?>?)!
|
||||
.cast<UniversalBleScanResult?>();
|
||||
return (__pigeon_replyList[0] as List<Object?>?)!.cast<UniversalBleScanResult?>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -625,7 +621,7 @@ class _UniversalBleCallbackChannelCodec extends StandardMessageCodec {
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 128:
|
||||
case 128:
|
||||
return UniversalBleScanResult.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
@@ -635,8 +631,7 @@ class _UniversalBleCallbackChannelCodec extends StandardMessageCodec {
|
||||
|
||||
/// Native -> Flutter
|
||||
abstract class UniversalBleCallbackChannel {
|
||||
static const MessageCodec<Object?> pigeonChannelCodec =
|
||||
_UniversalBleCallbackChannelCodec();
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _UniversalBleCallbackChannelCodec();
|
||||
|
||||
void onAvailabilityChanged(int state);
|
||||
|
||||
@@ -644,25 +639,21 @@ abstract class UniversalBleCallbackChannel {
|
||||
|
||||
void onScanResult(UniversalBleScanResult result);
|
||||
|
||||
void onValueChanged(
|
||||
String deviceId, String characteristicId, Uint8List value);
|
||||
void onValueChanged(String deviceId, String characteristicId, Uint8List value);
|
||||
|
||||
void onConnectionChanged(String deviceId, int state);
|
||||
|
||||
static void setup(UniversalBleCallbackChannel? api,
|
||||
{BinaryMessenger? binaryMessenger}) {
|
||||
static void setup(UniversalBleCallbackChannel? api, {BinaryMessenger? binaryMessenger}) {
|
||||
{
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
|
||||
Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged',
|
||||
pigeonChannelCodec,
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged', pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
if (api == null) {
|
||||
__pigeon_channel.setMessageHandler(null);
|
||||
} else {
|
||||
__pigeon_channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged was null.');
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final int? arg_state = (args[0] as int?);
|
||||
assert(arg_state != null,
|
||||
@@ -672,25 +663,22 @@ abstract class UniversalBleCallbackChannel {
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()));
|
||||
} catch (e) {
|
||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
|
||||
Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange',
|
||||
pigeonChannelCodec,
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange', pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
if (api == null) {
|
||||
__pigeon_channel.setMessageHandler(null);
|
||||
} else {
|
||||
__pigeon_channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange was null.');
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_deviceId = (args[0] as String?);
|
||||
assert(arg_deviceId != null,
|
||||
@@ -704,28 +692,24 @@ abstract class UniversalBleCallbackChannel {
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()));
|
||||
} catch (e) {
|
||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
|
||||
Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult',
|
||||
pigeonChannelCodec,
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult', pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
if (api == null) {
|
||||
__pigeon_channel.setMessageHandler(null);
|
||||
} else {
|
||||
__pigeon_channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult was null.');
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final UniversalBleScanResult? arg_result =
|
||||
(args[0] as UniversalBleScanResult?);
|
||||
final UniversalBleScanResult? arg_result = (args[0] as UniversalBleScanResult?);
|
||||
assert(arg_result != null,
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult was null, expected non-null UniversalBleScanResult.');
|
||||
try {
|
||||
@@ -733,25 +717,22 @@ abstract class UniversalBleCallbackChannel {
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()));
|
||||
} catch (e) {
|
||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
|
||||
Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged',
|
||||
pigeonChannelCodec,
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged', pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
if (api == null) {
|
||||
__pigeon_channel.setMessageHandler(null);
|
||||
} else {
|
||||
__pigeon_channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged was null.');
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_deviceId = (args[0] as String?);
|
||||
assert(arg_deviceId != null,
|
||||
@@ -763,30 +744,26 @@ abstract class UniversalBleCallbackChannel {
|
||||
assert(arg_value != null,
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged was null, expected non-null Uint8List.');
|
||||
try {
|
||||
api.onValueChanged(
|
||||
arg_deviceId!, arg_characteristicId!, arg_value!);
|
||||
api.onValueChanged(arg_deviceId!, arg_characteristicId!, arg_value!);
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()));
|
||||
} catch (e) {
|
||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
{
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
|
||||
Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged',
|
||||
pigeonChannelCodec,
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged', pigeonChannelCodec,
|
||||
binaryMessenger: binaryMessenger);
|
||||
if (api == null) {
|
||||
__pigeon_channel.setMessageHandler(null);
|
||||
} else {
|
||||
__pigeon_channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged was null.');
|
||||
'Argument for dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final String? arg_deviceId = (args[0] as String?);
|
||||
assert(arg_deviceId != null,
|
||||
@@ -799,9 +776,8 @@ abstract class UniversalBleCallbackChannel {
|
||||
return wrapResponse(empty: true);
|
||||
} on PlatformException catch (e) {
|
||||
return wrapResponse(error: e);
|
||||
} catch (e) {
|
||||
return wrapResponse(
|
||||
error: PlatformException(code: 'error', message: e.toString()));
|
||||
} catch (e) {
|
||||
return wrapResponse(error: PlatformException(code: 'error', message: e.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
|
||||
|
||||
@override
|
||||
Future<void> startScan({
|
||||
WebRequestOptionsBuilder? webRequestOptions,
|
||||
ScanFilter? scanFilter,
|
||||
}) async {
|
||||
await _channel.startScan(
|
||||
@@ -207,12 +206,19 @@ extension _UniversalBleScanResultExtension on UniversalBleScanResult {
|
||||
|
||||
extension _ScanFilterExtension on ScanFilter? {
|
||||
UniversalScanFilter? toUniversalScanFilter() {
|
||||
List<UniversalManufacturerDataFilter>? manufacturerDataFilters = this
|
||||
?.withManufacturerData
|
||||
.map((e) => UniversalManufacturerDataFilter(
|
||||
companyIdentifier: e.companyIdentifier,
|
||||
data: e.data,
|
||||
mask: e.mask,
|
||||
))
|
||||
.toList();
|
||||
|
||||
// Windows crashes if it's null, so we need to pass empty scan filter in this case
|
||||
if (this == null && Platform.isWindows) {
|
||||
return UniversalScanFilter(withServices: []);
|
||||
}
|
||||
return UniversalScanFilter(
|
||||
withServices: this?.withServices.toValidUUIDList() ?? [],
|
||||
withManufacturerData: manufacturerDataFilters ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ abstract class UniversalBlePlatform {
|
||||
Future<bool> enableBluetooth();
|
||||
|
||||
Future<void> startScan({
|
||||
WebRequestOptionsBuilder? webRequestOptions,
|
||||
ScanFilter? scanFilter,
|
||||
});
|
||||
|
||||
|
||||
@@ -109,22 +109,11 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
|
||||
@override
|
||||
Future<void> startScan({
|
||||
WebRequestOptionsBuilder? webRequestOptions,
|
||||
ScanFilter? scanFilter,
|
||||
}) async {
|
||||
RequestOptionsBuilder requestFilterBuilder;
|
||||
if (webRequestOptions != null) {
|
||||
requestFilterBuilder = webRequestOptions.toRequestOptionsBuilder(
|
||||
scanFilter: scanFilter,
|
||||
);
|
||||
} else if (scanFilter != null) {
|
||||
requestFilterBuilder = scanFilter.toRequestOptionsBuilder();
|
||||
} else {
|
||||
requestFilterBuilder = RequestOptionsBuilder.acceptAllDevices();
|
||||
}
|
||||
|
||||
BluetoothDevice device = await FlutterWebBluetooth.instance.requestDevice(
|
||||
requestFilterBuilder,
|
||||
scanFilter?.toRequestOptionsBuilder() ??
|
||||
RequestOptionsBuilder.acceptAllDevices(),
|
||||
);
|
||||
|
||||
// Update local device list
|
||||
@@ -389,8 +378,37 @@ extension _UnmodifiableMapViewExtension on UnmodifiableMapView<int, ByteData> {
|
||||
|
||||
extension ScanFilterExtension on ScanFilter {
|
||||
RequestOptionsBuilder toRequestOptionsBuilder() {
|
||||
List<RequestFilterBuilder> filters = [];
|
||||
// Add services filter
|
||||
for (var service in withServices.toValidUUIDList()) {
|
||||
filters.add(
|
||||
RequestFilterBuilder(
|
||||
services: [service],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Add manufacturer data filter
|
||||
for (var manufacturerData in withManufacturerData) {
|
||||
filters.add(
|
||||
RequestFilterBuilder(
|
||||
manufacturerData: [
|
||||
ManufacturerDataFilterBuilder(
|
||||
companyIdentifier: manufacturerData.companyIdentifier,
|
||||
dataPrefix: manufacturerData.data,
|
||||
mask: manufacturerData.mask,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (filters.isEmpty) {
|
||||
return RequestOptionsBuilder.acceptAllDevices();
|
||||
}
|
||||
|
||||
return RequestOptionsBuilder(
|
||||
[RequestFilterBuilder(services: withServices.toValidUUIDList())],
|
||||
filters,
|
||||
optionalServices: withServices.toValidUUIDList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,8 +130,24 @@ class UniversalBleCharacteristic {
|
||||
UniversalBleCharacteristic(this.uuid, this.properties);
|
||||
}
|
||||
|
||||
/// Scan Filters
|
||||
class UniversalScanFilter {
|
||||
final List<String?> withServices;
|
||||
final List<UniversalManufacturerDataFilter?> withManufacturerData;
|
||||
|
||||
UniversalScanFilter(this.withServices);
|
||||
UniversalScanFilter(
|
||||
this.withServices,
|
||||
this.withManufacturerData,
|
||||
);
|
||||
}
|
||||
|
||||
class UniversalManufacturerDataFilter {
|
||||
int? companyIdentifier;
|
||||
Uint8List? data;
|
||||
Uint8List? mask;
|
||||
UniversalManufacturerDataFilter({
|
||||
this.companyIdentifier,
|
||||
this.data,
|
||||
this.mask,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -273,8 +273,11 @@ UniversalBleCharacteristic UniversalBleCharacteristic::FromEncodableList(const E
|
||||
|
||||
// UniversalScanFilter
|
||||
|
||||
UniversalScanFilter::UniversalScanFilter(const EncodableList& with_services)
|
||||
: with_services_(with_services) {}
|
||||
UniversalScanFilter::UniversalScanFilter(
|
||||
const EncodableList& with_services,
|
||||
const EncodableList& with_manufacturer_data)
|
||||
: with_services_(with_services),
|
||||
with_manufacturer_data_(with_manufacturer_data) {}
|
||||
|
||||
const EncodableList& UniversalScanFilter::with_services() const {
|
||||
return with_services_;
|
||||
@@ -285,16 +288,104 @@ void UniversalScanFilter::set_with_services(const EncodableList& value_arg) {
|
||||
}
|
||||
|
||||
|
||||
const EncodableList& UniversalScanFilter::with_manufacturer_data() const {
|
||||
return with_manufacturer_data_;
|
||||
}
|
||||
|
||||
void UniversalScanFilter::set_with_manufacturer_data(const EncodableList& value_arg) {
|
||||
with_manufacturer_data_ = value_arg;
|
||||
}
|
||||
|
||||
|
||||
EncodableList UniversalScanFilter::ToEncodableList() const {
|
||||
EncodableList list;
|
||||
list.reserve(1);
|
||||
list.reserve(2);
|
||||
list.push_back(EncodableValue(with_services_));
|
||||
list.push_back(EncodableValue(with_manufacturer_data_));
|
||||
return list;
|
||||
}
|
||||
|
||||
UniversalScanFilter UniversalScanFilter::FromEncodableList(const EncodableList& list) {
|
||||
UniversalScanFilter decoded(
|
||||
std::get<EncodableList>(list[0]));
|
||||
std::get<EncodableList>(list[0]),
|
||||
std::get<EncodableList>(list[1]));
|
||||
return decoded;
|
||||
}
|
||||
|
||||
// UniversalManufacturerDataFilter
|
||||
|
||||
UniversalManufacturerDataFilter::UniversalManufacturerDataFilter() {}
|
||||
|
||||
UniversalManufacturerDataFilter::UniversalManufacturerDataFilter(
|
||||
const int64_t* company_identifier,
|
||||
const std::vector<uint8_t>* data,
|
||||
const std::vector<uint8_t>* mask)
|
||||
: company_identifier_(company_identifier ? std::optional<int64_t>(*company_identifier) : std::nullopt),
|
||||
data_(data ? std::optional<std::vector<uint8_t>>(*data) : std::nullopt),
|
||||
mask_(mask ? std::optional<std::vector<uint8_t>>(*mask) : std::nullopt) {}
|
||||
|
||||
const int64_t* UniversalManufacturerDataFilter::company_identifier() const {
|
||||
return company_identifier_ ? &(*company_identifier_) : nullptr;
|
||||
}
|
||||
|
||||
void UniversalManufacturerDataFilter::set_company_identifier(const int64_t* value_arg) {
|
||||
company_identifier_ = value_arg ? std::optional<int64_t>(*value_arg) : std::nullopt;
|
||||
}
|
||||
|
||||
void UniversalManufacturerDataFilter::set_company_identifier(int64_t value_arg) {
|
||||
company_identifier_ = value_arg;
|
||||
}
|
||||
|
||||
|
||||
const std::vector<uint8_t>* UniversalManufacturerDataFilter::data() const {
|
||||
return data_ ? &(*data_) : nullptr;
|
||||
}
|
||||
|
||||
void UniversalManufacturerDataFilter::set_data(const std::vector<uint8_t>* value_arg) {
|
||||
data_ = value_arg ? std::optional<std::vector<uint8_t>>(*value_arg) : std::nullopt;
|
||||
}
|
||||
|
||||
void UniversalManufacturerDataFilter::set_data(const std::vector<uint8_t>& value_arg) {
|
||||
data_ = value_arg;
|
||||
}
|
||||
|
||||
|
||||
const std::vector<uint8_t>* UniversalManufacturerDataFilter::mask() const {
|
||||
return mask_ ? &(*mask_) : nullptr;
|
||||
}
|
||||
|
||||
void UniversalManufacturerDataFilter::set_mask(const std::vector<uint8_t>* value_arg) {
|
||||
mask_ = value_arg ? std::optional<std::vector<uint8_t>>(*value_arg) : std::nullopt;
|
||||
}
|
||||
|
||||
void UniversalManufacturerDataFilter::set_mask(const std::vector<uint8_t>& value_arg) {
|
||||
mask_ = value_arg;
|
||||
}
|
||||
|
||||
|
||||
EncodableList UniversalManufacturerDataFilter::ToEncodableList() const {
|
||||
EncodableList list;
|
||||
list.reserve(3);
|
||||
list.push_back(company_identifier_ ? EncodableValue(*company_identifier_) : EncodableValue());
|
||||
list.push_back(data_ ? EncodableValue(*data_) : EncodableValue());
|
||||
list.push_back(mask_ ? EncodableValue(*mask_) : EncodableValue());
|
||||
return list;
|
||||
}
|
||||
|
||||
UniversalManufacturerDataFilter UniversalManufacturerDataFilter::FromEncodableList(const EncodableList& list) {
|
||||
UniversalManufacturerDataFilter decoded;
|
||||
auto& encodable_company_identifier = list[0];
|
||||
if (!encodable_company_identifier.IsNull()) {
|
||||
decoded.set_company_identifier(encodable_company_identifier.LongValue());
|
||||
}
|
||||
auto& encodable_data = list[1];
|
||||
if (!encodable_data.IsNull()) {
|
||||
decoded.set_data(std::get<std::vector<uint8_t>>(encodable_data));
|
||||
}
|
||||
auto& encodable_mask = list[2];
|
||||
if (!encodable_mask.IsNull()) {
|
||||
decoded.set_mask(std::get<std::vector<uint8_t>>(encodable_mask));
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
@@ -312,6 +403,8 @@ EncodableValue UniversalBlePlatformChannelCodecSerializer::ReadValueOfType(
|
||||
case 130:
|
||||
return CustomEncodableValue(UniversalBleService::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
|
||||
case 131:
|
||||
return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
|
||||
case 132:
|
||||
return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
|
||||
default:
|
||||
return flutter::StandardCodecSerializer::ReadValueOfType(type, stream);
|
||||
@@ -337,8 +430,13 @@ void UniversalBlePlatformChannelCodecSerializer::WriteValue(
|
||||
WriteValue(EncodableValue(std::any_cast<UniversalBleService>(*custom_value).ToEncodableList()), stream);
|
||||
return;
|
||||
}
|
||||
if (custom_value->type() == typeid(UniversalScanFilter)) {
|
||||
if (custom_value->type() == typeid(UniversalManufacturerDataFilter)) {
|
||||
stream->WriteByte(131);
|
||||
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerDataFilter>(*custom_value).ToEncodableList()), stream);
|
||||
return;
|
||||
}
|
||||
if (custom_value->type() == typeid(UniversalScanFilter)) {
|
||||
stream->WriteByte(132);
|
||||
WriteValue(EncodableValue(std::any_cast<UniversalScanFilter>(*custom_value).ToEncodableList()), stream);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -179,15 +179,22 @@ class UniversalBleCharacteristic {
|
||||
};
|
||||
|
||||
|
||||
// Scan Filters
|
||||
//
|
||||
// Generated class from Pigeon that represents data sent in messages.
|
||||
class UniversalScanFilter {
|
||||
public:
|
||||
// Constructs an object setting all fields.
|
||||
explicit UniversalScanFilter(const flutter::EncodableList& with_services);
|
||||
explicit UniversalScanFilter(
|
||||
const flutter::EncodableList& with_services,
|
||||
const flutter::EncodableList& with_manufacturer_data);
|
||||
|
||||
const flutter::EncodableList& with_services() const;
|
||||
void set_with_services(const flutter::EncodableList& value_arg);
|
||||
|
||||
const flutter::EncodableList& with_manufacturer_data() const;
|
||||
void set_with_manufacturer_data(const flutter::EncodableList& value_arg);
|
||||
|
||||
|
||||
private:
|
||||
static UniversalScanFilter FromEncodableList(const flutter::EncodableList& list);
|
||||
@@ -197,6 +204,46 @@ class UniversalScanFilter {
|
||||
friend class UniversalBleCallbackChannel;
|
||||
friend class UniversalBleCallbackChannelCodecSerializer;
|
||||
flutter::EncodableList with_services_;
|
||||
flutter::EncodableList with_manufacturer_data_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Generated class from Pigeon that represents data sent in messages.
|
||||
class UniversalManufacturerDataFilter {
|
||||
public:
|
||||
// Constructs an object setting all non-nullable fields.
|
||||
UniversalManufacturerDataFilter();
|
||||
|
||||
// Constructs an object setting all fields.
|
||||
explicit UniversalManufacturerDataFilter(
|
||||
const int64_t* company_identifier,
|
||||
const std::vector<uint8_t>* data,
|
||||
const std::vector<uint8_t>* mask);
|
||||
|
||||
const int64_t* company_identifier() const;
|
||||
void set_company_identifier(const int64_t* value_arg);
|
||||
void set_company_identifier(int64_t value_arg);
|
||||
|
||||
const std::vector<uint8_t>* data() const;
|
||||
void set_data(const std::vector<uint8_t>* value_arg);
|
||||
void set_data(const std::vector<uint8_t>& value_arg);
|
||||
|
||||
const std::vector<uint8_t>* mask() const;
|
||||
void set_mask(const std::vector<uint8_t>* value_arg);
|
||||
void set_mask(const std::vector<uint8_t>& value_arg);
|
||||
|
||||
|
||||
private:
|
||||
static UniversalManufacturerDataFilter FromEncodableList(const flutter::EncodableList& list);
|
||||
flutter::EncodableList ToEncodableList() const;
|
||||
friend class UniversalBlePlatformChannel;
|
||||
friend class UniversalBlePlatformChannelCodecSerializer;
|
||||
friend class UniversalBleCallbackChannel;
|
||||
friend class UniversalBleCallbackChannelCodecSerializer;
|
||||
std::optional<int64_t> company_identifier_;
|
||||
std::optional<std::vector<uint8_t>> data_;
|
||||
std::optional<std::vector<uint8_t>> mask_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace universal_ble
|
||||
const auto signalStrengthKey = L"System.Devices.Aep.SignalStrength";
|
||||
|
||||
std::unique_ptr<UniversalBleCallbackChannel> callbackChannel;
|
||||
std::unordered_map<std::string, winrt::event_token> characteristicsTokens{}; // TODO: Remove the map and store the token inside the characteristic object object
|
||||
std::unordered_map<std::string, winrt::event_token> characteristicsTokens{}; // TODO: Remove the map and store the token inside the characteristic object
|
||||
std::vector<UniversalManufacturerDataFilter> manufacturerScanFilter = std::vector<UniversalManufacturerDataFilter>();
|
||||
|
||||
void UniversalBlePlugin::RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar)
|
||||
{
|
||||
@@ -89,21 +90,49 @@ namespace universal_ble
|
||||
{
|
||||
if (bluetoothRadio && bluetoothRadio.State() == RadioState::On)
|
||||
{
|
||||
setupDeviceWatcher();
|
||||
DeviceWatcherStatus status = deviceWatcher.Status();
|
||||
if (status != DeviceWatcherStatus::Started)
|
||||
{
|
||||
deviceWatcher.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
return FlutterError("Already scanning");
|
||||
}
|
||||
|
||||
if (!bluetoothLEWatcher)
|
||||
{
|
||||
bluetoothLEWatcher = BluetoothLEAdvertisementWatcher();
|
||||
bluetoothLEWatcher.ScanningMode(BluetoothLEScanningMode::Active);
|
||||
|
||||
// reset scan filters
|
||||
manufacturerScanFilter.clear();
|
||||
if (filter != nullptr)
|
||||
ApplyScanFilter(filter, bluetoothLEWatcher);
|
||||
{
|
||||
// Apply Services filter
|
||||
const auto &services = filter->with_services();
|
||||
if (!services.empty())
|
||||
{
|
||||
for (const auto &uuid : services)
|
||||
{
|
||||
std::string uuid_str = std::get<std::string>(uuid);
|
||||
bluetoothLEWatcher.AdvertisementFilter().Advertisement().ServiceUuids().Append(uuid_to_guid(uuid_str));
|
||||
}
|
||||
}
|
||||
|
||||
// Set ManufacturerData filter
|
||||
const auto &manufacturerData = filter->with_manufacturer_data();
|
||||
for (const flutter::EncodableValue &data : manufacturerData)
|
||||
{
|
||||
UniversalManufacturerDataFilter manufacturerDataFilter = std::any_cast<UniversalManufacturerDataFilter>(std::get<flutter::CustomEncodableValue>(data));
|
||||
manufacturerScanFilter.push_back(manufacturerDataFilter);
|
||||
}
|
||||
}
|
||||
bluetoothLEWatcherReceivedToken = bluetoothLEWatcher.Received({this, &UniversalBlePlugin::BluetoothLEWatcher_Received});
|
||||
}
|
||||
bluetoothLEWatcher.Start();
|
||||
setupDeviceWatcher();
|
||||
DeviceWatcherStatus status = deviceWatcher.Status();
|
||||
if (status != DeviceWatcherStatus::Started)
|
||||
deviceWatcher.Start();
|
||||
else
|
||||
return FlutterError("Already scanning");
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
@@ -112,20 +141,6 @@ namespace universal_ble
|
||||
}
|
||||
};
|
||||
|
||||
void UniversalBlePlugin::ApplyScanFilter(const UniversalScanFilter *filter, BluetoothLEAdvertisementWatcher &bluetoothWatcher)
|
||||
{
|
||||
// Apply Services filter
|
||||
const auto &services = filter->with_services();
|
||||
if (!services.empty())
|
||||
{
|
||||
for (const auto &uuid : services)
|
||||
{
|
||||
std::string uuid_str = std::get<std::string>(uuid);
|
||||
bluetoothWatcher.AdvertisementFilter().Advertisement().ServiceUuids().Append(uuid_to_guid(uuid_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<FlutterError> UniversalBlePlugin::StopScan()
|
||||
{
|
||||
|
||||
@@ -688,6 +703,17 @@ namespace universal_ble
|
||||
if (!args.IsConnectable())
|
||||
return;
|
||||
|
||||
// Apply ManufacturerData filter
|
||||
if (!manufacturerScanFilter.empty())
|
||||
{
|
||||
// Avoid devices if ManufacturerData is not present
|
||||
if (args.Advertisement().ManufacturerData().Size() == 0)
|
||||
return;
|
||||
// Avoid devices if ManufacturerData does not match the filter
|
||||
if (!filterByManufacturerData(args.Advertisement().ManufacturerData()))
|
||||
return;
|
||||
}
|
||||
|
||||
auto deviceId = _mac_address_to_str(args.BluetoothAddress());
|
||||
auto universalScanResult = UniversalBleScanResult(deviceId);
|
||||
std::string name = winrt::to_string(args.Advertisement().LocalName());
|
||||
@@ -747,6 +773,63 @@ namespace universal_ble
|
||||
}
|
||||
}
|
||||
|
||||
bool UniversalBlePlugin::filterByManufacturerData(IVector<BluetoothLEManufacturerData> deviceManufactureData)
|
||||
{
|
||||
if (manufacturerScanFilter.empty())
|
||||
return true;
|
||||
|
||||
for (auto &&filter : manufacturerScanFilter)
|
||||
{
|
||||
const int64_t *company_identifier = filter.company_identifier();
|
||||
const std::vector<uint8_t> *data_filter = filter.data();
|
||||
const std::vector<uint8_t> *mask = filter.mask();
|
||||
|
||||
if (company_identifier == nullptr)
|
||||
continue;
|
||||
|
||||
uint16_t companyId = static_cast<uint16_t>(*company_identifier);
|
||||
|
||||
for (auto &&deviceMfData : deviceManufactureData)
|
||||
{
|
||||
if (deviceMfData.CompanyId() == companyId)
|
||||
{
|
||||
// If data filter is not present then return true
|
||||
if (data_filter == nullptr)
|
||||
return true;
|
||||
|
||||
auto deviceData = to_bytevc(deviceMfData.Data());
|
||||
if (deviceData.size() < data_filter->size())
|
||||
continue;
|
||||
|
||||
bool isMatch = true;
|
||||
for (size_t i = 0; i < data_filter->size(); i++)
|
||||
{
|
||||
if (mask != nullptr && mask->size() > i)
|
||||
{
|
||||
if (((*mask)[i] & (*data_filter)[i]) != ((*mask)[i] & deviceData[i]))
|
||||
{
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((*data_filter)[i] != deviceData[i])
|
||||
{
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isMatch)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AvailabilityState UniversalBlePlugin::getAvailabilityStateFromRadio(RadioState radioState)
|
||||
{
|
||||
auto state = [=]() -> AvailabilityState
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace universal_ble
|
||||
winrt::fire_and_forget WriteAsync(GattCharacteristic characteristic, GattWriteOption writeOption,
|
||||
const std::vector<uint8_t> &value,
|
||||
std::function<void(std::optional<FlutterError> reply)> result);
|
||||
void ApplyScanFilter(const UniversalScanFilter *filter, BluetoothLEAdvertisementWatcher &bluetoothWatcher);
|
||||
bool filterByManufacturerData(IVector<BluetoothLEManufacturerData> deviceManufactureData);
|
||||
|
||||
// UniversalBlePlatformChannel implementation.
|
||||
void GetBluetoothAvailabilityState(std::function<void(ErrorOr<int64_t> reply)> result) override;
|
||||
|
||||
Reference in New Issue
Block a user