Improve ManufacturerDataFilter (#139)
* Improve-ManufacturerDataFilter-docs * Improve ManufacturerDataFilter namings and code * Perform more renamings Add tests
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class ManufacturerDataFilter {
|
||||
/// Must be of integer type, in hex or decimal form (e.g. 0x004c or 76).
|
||||
int companyIdentifier;
|
||||
|
||||
/// Matches as prefix the peripheral's advertised data.
|
||||
Uint8List? payloadPrefix;
|
||||
|
||||
/// For each bit in the mask, set it to 1 if it needs to match
|
||||
/// the corresponding one in manufacturer data, or otherwise set it to 0.
|
||||
/// The 'mask' must have the same length as the payload.
|
||||
Uint8List? payloadMask;
|
||||
|
||||
/// Filter manufacturer data by company identifier, payload prefix, or payload mask.
|
||||
ManufacturerDataFilter({
|
||||
required this.companyIdentifier,
|
||||
this.payloadPrefix,
|
||||
this.payloadMask,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ManufacturerDataFilter(companyIdentifier: $companyIdentifier, payloadPrefix: $payloadPrefix, mask: $payloadMask)';
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ export 'package:universal_ble/src/models/platform_config.dart';
|
||||
export 'package:universal_ble/src/models/queue_type.dart';
|
||||
export 'package:universal_ble/src/models/ble_uuid_parser.dart';
|
||||
export 'package:universal_ble/src/models/scan_filter.dart';
|
||||
export 'package:universal_ble/src/models/manufacturer_data_filter.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,4 +1,4 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:universal_ble/src/models/manufacturer_data_filter.dart';
|
||||
|
||||
class ScanFilter {
|
||||
List<String> withServices;
|
||||
@@ -16,26 +16,3 @@ class ScanFilter {
|
||||
return 'ScanFilter(withServices: $withServices, withManufacturerData: $withManufacturerData, withNamePrefix: $withNamePrefix)';
|
||||
}
|
||||
}
|
||||
|
||||
class ManufacturerDataFilter {
|
||||
int companyIdentifier;
|
||||
|
||||
// Mask and data must be of same length
|
||||
Uint8List? payload;
|
||||
|
||||
/// 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({
|
||||
required this.companyIdentifier,
|
||||
this.payload,
|
||||
this.mask,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ManufacturerDataFilter(companyIdentifier: $companyIdentifier, payload: $payload, mask: $mask)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:universal_ble/universal_ble.dart';
|
||||
class UniversalBleFilterUtil {
|
||||
ScanFilter? scanFilter;
|
||||
|
||||
bool filterDevice(BleDevice device) {
|
||||
bool matchesDevice(BleDevice device) {
|
||||
final filter = scanFilter;
|
||||
if (filter == null) return true;
|
||||
|
||||
@@ -23,13 +23,12 @@ class UniversalBleFilterUtil {
|
||||
}
|
||||
|
||||
// Else check one of the filter passes
|
||||
return hasNamePrefixFilter && isNameMatchingFilters(filter, device) ||
|
||||
hasServiceFilter && isServicesMatchingFilters(filter, device) ||
|
||||
hasManufacturerDataFilter &&
|
||||
isManufacturerDataMatchingFilters(filter, device);
|
||||
return hasNamePrefixFilter && nameMatches(filter, device) ||
|
||||
hasServiceFilter && servicesMatch(filter, device) ||
|
||||
hasManufacturerDataFilter && manufacturerDataMatches(filter, device);
|
||||
}
|
||||
|
||||
bool isNameMatchingFilters(ScanFilter scanFilter, BleDevice device) {
|
||||
bool nameMatches(ScanFilter scanFilter, BleDevice device) {
|
||||
var namePrefixFilter = scanFilter.withNamePrefix;
|
||||
if (namePrefixFilter.isEmpty) return true;
|
||||
|
||||
@@ -38,7 +37,7 @@ class UniversalBleFilterUtil {
|
||||
return namePrefixFilter.any(name.startsWith);
|
||||
}
|
||||
|
||||
bool isServicesMatchingFilters(ScanFilter scanFilter, BleDevice device) {
|
||||
bool servicesMatch(ScanFilter scanFilter, BleDevice device) {
|
||||
var serviceFilters = scanFilter.withServices;
|
||||
if (serviceFilters.isEmpty) return true;
|
||||
|
||||
@@ -49,48 +48,56 @@ class UniversalBleFilterUtil {
|
||||
return serviceFilters.any(serviceUuids.contains);
|
||||
}
|
||||
|
||||
bool isManufacturerDataMatchingFilters(
|
||||
ScanFilter scanFilter,
|
||||
BleDevice device,
|
||||
) {
|
||||
bool manufacturerDataMatches(ScanFilter scanFilter, BleDevice device) {
|
||||
final manufacturerDataFilters = scanFilter.withManufacturerData;
|
||||
if (manufacturerDataFilters.isEmpty) return true;
|
||||
|
||||
List<ManufacturerData> manufacturerDataList = device.manufacturerDataList;
|
||||
if (manufacturerDataList.isEmpty) return false;
|
||||
List<ManufacturerData> deviceDataList = device.manufacturerDataList;
|
||||
if (deviceDataList.isEmpty) return false;
|
||||
|
||||
return manufacturerDataList.any((deviceMsd) => manufacturerDataFilters.any(
|
||||
(filterMsd) => _isManufacturerDataMatch(filterMsd, deviceMsd),
|
||||
));
|
||||
return deviceDataList
|
||||
.any((deviceData) => manufacturerDataFilters.any((filter) {
|
||||
// Early return if company identifiers don't match
|
||||
if (filter.companyIdentifier != deviceData.companyId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final payloadPrefix = filter.payloadPrefix;
|
||||
final payload = deviceData.payload;
|
||||
|
||||
// Handle cases where payload prefix is null or empty
|
||||
if (payloadPrefix == null || payloadPrefix.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate payload lengths
|
||||
if (payload.isEmpty || payloadPrefix.length > payload.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final filterMask = filter.payloadMask;
|
||||
|
||||
// Choose comparison strategy based on filter mask
|
||||
return filterMask != null &&
|
||||
filterMask.length == payloadPrefix.length
|
||||
? _compareWithMask(payloadPrefix, payload, filterMask)
|
||||
: _compareWithoutMask(payloadPrefix, payload);
|
||||
}));
|
||||
}
|
||||
|
||||
bool _isManufacturerDataMatch(
|
||||
ManufacturerDataFilter filterMsd,
|
||||
ManufacturerData deviceMsd,
|
||||
) {
|
||||
if (filterMsd.companyIdentifier != deviceMsd.companyId) return false;
|
||||
|
||||
Uint8List? filterPayload = filterMsd.payload;
|
||||
Uint8List devicePayload = deviceMsd.payload;
|
||||
|
||||
if (filterPayload == null || filterPayload.isEmpty) return true;
|
||||
if (devicePayload.isEmpty) return false;
|
||||
if (filterPayload.length > devicePayload.length) return false;
|
||||
|
||||
Uint8List? filterMask = filterMsd.mask;
|
||||
|
||||
if (filterMask != null && filterMask.length == filterPayload.length) {
|
||||
for (int i = 0; i < filterPayload.length; i++) {
|
||||
if ((filterPayload[i] & filterMask[i]) !=
|
||||
(devicePayload[i] & filterMask[i])) {
|
||||
return false;
|
||||
}
|
||||
bool _compareWithMask(Uint8List prefix, Uint8List payload, Uint8List mask) {
|
||||
for (int i = 0; i < prefix.length; i++) {
|
||||
if ((prefix[i] & mask[i]) != (payload[i] & mask[i])) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < filterPayload.length; i++) {
|
||||
if (filterPayload[i] != devicePayload[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _compareWithoutMask(Uint8List prefix, Uint8List payload) {
|
||||
for (int i = 0; i < prefix.length; i++) {
|
||||
if (prefix[i] != payload[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -107,12 +107,12 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
_deviceAdded = null;
|
||||
_deviceRemoved = null;
|
||||
|
||||
// Stop Disovery
|
||||
// Stop Discovery
|
||||
if (_activeAdapter?.discovering == true) {
|
||||
await _activeAdapter?.stopDiscovery();
|
||||
}
|
||||
|
||||
// Clean all advertiseemnt listeners
|
||||
// Clean all advertisement listeners
|
||||
_deviceAdvertisementSubscriptions.removeWhere((e, value) {
|
||||
value.cancel();
|
||||
return true;
|
||||
@@ -455,7 +455,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
|
||||
void _onDeviceAdd(BlueZDevice device) {
|
||||
BleDevice bleDevice = device.toBleDevice();
|
||||
if (!_bleFilter.filterDevice(bleDevice)) {
|
||||
if (!_bleFilter.matchesDevice(bleDevice)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
e.contains(BluezProperty.manufacturerData) ||
|
||||
e.contains(BluezProperty.uuids))
|
||||
.listen((_) {
|
||||
if (_bleFilter.filterDevice(bleDevice)) {
|
||||
if (_bleFilter.matchesDevice(bleDevice)) {
|
||||
updateScanResult(device.toBleDevice());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -245,8 +245,8 @@ extension _ScanFilterExtension on ScanFilter? {
|
||||
?.withManufacturerData
|
||||
.map((e) => UniversalManufacturerDataFilter(
|
||||
companyIdentifier: e.companyIdentifier,
|
||||
data: e.payload,
|
||||
mask: e.mask,
|
||||
data: e.payloadPrefix,
|
||||
mask: e.payloadMask,
|
||||
))
|
||||
.toList();
|
||||
|
||||
|
||||
@@ -369,8 +369,8 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
manufacturerData: [
|
||||
ManufacturerDataFilterBuilder(
|
||||
companyIdentifier: manufacturerData.companyIdentifier,
|
||||
dataPrefix: manufacturerData.payload,
|
||||
mask: manufacturerData.mask,
|
||||
dataPrefix: manufacturerData.payloadPrefix,
|
||||
mask: manufacturerData.payloadMask,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user