Improve scan filter api (#78)
* Improve scan filter api * Implement Windows filters * Add manufacturerDataList and deprecate manufacturerData * Fix Windows * Update Readme and changelog * Remove import * Fix Linux * Update readme and changelog * Cleanup Todo * Improve ManufacturerData * Rename webConfig and Manufacturer.data * Return false for receivesAdvertisements on Linux/Web * Add delay in discoverServices on Linux * Remove import * Update changelog --------- Co-authored-by: fotidim <foti@navideck.com>
This commit is contained in:
Vendored
-2
@@ -8,7 +8,6 @@
|
||||
"connnection",
|
||||
"Cupertino",
|
||||
"DFACE",
|
||||
"DSIMPLEBLE",
|
||||
"HFONT",
|
||||
"HINSTANCE",
|
||||
"HMENU",
|
||||
@@ -45,7 +44,6 @@
|
||||
"rssi",
|
||||
"SETFONT",
|
||||
"SETTEXT",
|
||||
"simpleble",
|
||||
"subdata",
|
||||
"sublist",
|
||||
"THICKFRAME",
|
||||
|
||||
+11
-1
@@ -1,5 +1,15 @@
|
||||
## 0.12.1
|
||||
## 0.13.0
|
||||
* BREAKING CHANGE: `scanFilter` filters are now in OR relation
|
||||
* BREAKING CHANGE: `manufacturerDataHead` is removed from `BleDevice`
|
||||
* BREAKING CHANGE: rename `WebConfig` to `WebOptions`
|
||||
* BREAKING CHANGE: rename `ManufacturerDataFilter.data` to `ManufacturerDataFilter.payload`
|
||||
* Deprecation: `manufacturerData` is deprecated in BleDevice and will be removed in the future
|
||||
* Improve `scanFilter` handling
|
||||
* Use `ManufacturerData` object instead of `Uint8List` for manufacturerData
|
||||
* Add `manufacturerDataList` as `List<ManufacturerData>` in `BleDevice`
|
||||
* Auto convert all services passed to `getSystemDevices()`
|
||||
* Return false for receivesAdvertisements on Linux/Web
|
||||
* Add 1s delay in discoverServices on Linux
|
||||
|
||||
## 0.12.0
|
||||
* BREAKING CHANGE: `unPair` is now `unpair`
|
||||
|
||||
@@ -114,16 +114,18 @@ You still need to explicitly [connect](#connecting) to them before being able to
|
||||
|
||||
#### Scan Filter
|
||||
|
||||
You can optionally set filters when scanning.
|
||||
You can optionally set a filter when scanning. A filter can have multiple conditions (services, manufacturerData, namePrefix) and all conditions are in `OR` relation, returning results that match any of the given conditions.
|
||||
|
||||
##### With Services
|
||||
|
||||
When setting this parameter, the scan results will only include devices that advertise any of the specified services. This is the primary filter. All devices are first filtered by services, then further filtered by other criteria. This parameter is mandatory on [web](#web) if you want to access those services.
|
||||
When setting this parameter, the scan results will only include devices that advertise any of the specified services.
|
||||
|
||||
```dart
|
||||
List<String> withServices;
|
||||
```
|
||||
|
||||
Note: On web **you have to** specify services before you are able to use them. See the [web](#web) section for more details.
|
||||
|
||||
##### With ManufacturerData
|
||||
|
||||
Use the `withManufacturerData` parameter to filter devices by manufacturer data. When you pass a list of `ManufacturerDataFilter` objects to this parameter, the scan results will only include devices that contain any of the specified manufacturer data.
|
||||
@@ -335,11 +337,23 @@ When publishing on Windows you need to declare the following [capabilities](http
|
||||
|
||||
### Web
|
||||
|
||||
On web, the `withServices` parameter in the ScanFilter is used as [optional_services](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/requestDevice#optionalservices) as well as a services filter. On web you have to set this parameter to ensure that you can access the specified services after connecting to the device. You can leave it empty for the rest of the platforms if your device does not advertise services.
|
||||
On web, the `withServices` parameter in the ScanFilter is used as [optional_services](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/requestDevice#optionalservices) as well as a services filter. You have to set this parameter to ensure that you can access the specified services after connecting to the device. You can leave it empty for the rest of the platforms if your device does not advertise services.
|
||||
|
||||
```dart
|
||||
ScanFilter(
|
||||
withServices: kIsWeb ? ["SERVICE_UUID"] : [],
|
||||
withServices: kIsWeb ? ["SERVICE_UUID"] : [],
|
||||
)
|
||||
```
|
||||
|
||||
If you don't want to apply any filter for these services but still want to access them, after connection, use `PlatformConfig`.
|
||||
|
||||
```dart
|
||||
UniversalBle.startScan(
|
||||
platformConfig: PlatformConfig(
|
||||
web: WebOptions(
|
||||
optionalServices: ["SERVICE_UUID"]
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -53,8 +53,7 @@ data class UniversalBleScanResult (
|
||||
val name: String? = null,
|
||||
val isPaired: Boolean? = null,
|
||||
val rssi: Long? = null,
|
||||
val manufacturerData: ByteArray? = null,
|
||||
val manufacturerDataHead: ByteArray? = null,
|
||||
val manufacturerDataList: List<UniversalManufacturerData?>? = null,
|
||||
val services: List<String?>? = null
|
||||
|
||||
) {
|
||||
@@ -65,10 +64,9 @@ data class UniversalBleScanResult (
|
||||
val name = __pigeon_list[1] as String?
|
||||
val isPaired = __pigeon_list[2] as Boolean?
|
||||
val rssi = __pigeon_list[3].let { num -> if (num is Int) num.toLong() else num as Long? }
|
||||
val manufacturerData = __pigeon_list[4] as ByteArray?
|
||||
val manufacturerDataHead = __pigeon_list[5] as ByteArray?
|
||||
val services = __pigeon_list[6] as List<String?>?
|
||||
return UniversalBleScanResult(deviceId, name, isPaired, rssi, manufacturerData, manufacturerDataHead, services)
|
||||
val manufacturerDataList = __pigeon_list[4] as List<UniversalManufacturerData?>?
|
||||
val services = __pigeon_list[5] as List<String?>?
|
||||
return UniversalBleScanResult(deviceId, name, isPaired, rssi, manufacturerDataList, services)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
@@ -77,8 +75,7 @@ data class UniversalBleScanResult (
|
||||
name,
|
||||
isPaired,
|
||||
rssi,
|
||||
manufacturerData,
|
||||
manufacturerDataHead,
|
||||
manufacturerDataList,
|
||||
services,
|
||||
)
|
||||
}
|
||||
@@ -135,6 +132,7 @@ data class UniversalBleCharacteristic (
|
||||
*/
|
||||
data class UniversalScanFilter (
|
||||
val withServices: List<String?>,
|
||||
val withNamePrefix: List<String?>,
|
||||
val withManufacturerData: List<UniversalManufacturerDataFilter?>
|
||||
|
||||
) {
|
||||
@@ -142,13 +140,15 @@ data class UniversalScanFilter (
|
||||
@Suppress("LocalVariableName")
|
||||
fun fromList(__pigeon_list: List<Any?>): UniversalScanFilter {
|
||||
val withServices = __pigeon_list[0] as List<String?>
|
||||
val withManufacturerData = __pigeon_list[1] as List<UniversalManufacturerDataFilter?>
|
||||
return UniversalScanFilter(withServices, withManufacturerData)
|
||||
val withNamePrefix = __pigeon_list[1] as List<String?>
|
||||
val withManufacturerData = __pigeon_list[2] as List<UniversalManufacturerDataFilter?>
|
||||
return UniversalScanFilter(withServices, withNamePrefix, withManufacturerData)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
return listOf(
|
||||
withServices,
|
||||
withNamePrefix,
|
||||
withManufacturerData,
|
||||
)
|
||||
}
|
||||
@@ -156,7 +156,7 @@ data class UniversalScanFilter (
|
||||
|
||||
/** Generated class from Pigeon that represents data sent in messages. */
|
||||
data class UniversalManufacturerDataFilter (
|
||||
val companyIdentifier: Long? = null,
|
||||
val companyIdentifier: Long,
|
||||
val data: ByteArray? = null,
|
||||
val mask: ByteArray? = null
|
||||
|
||||
@@ -164,7 +164,7 @@ data class UniversalManufacturerDataFilter (
|
||||
companion object {
|
||||
@Suppress("LocalVariableName")
|
||||
fun fromList(__pigeon_list: List<Any?>): UniversalManufacturerDataFilter {
|
||||
val companyIdentifier = __pigeon_list[0].let { num -> if (num is Int) num.toLong() else num as Long? }
|
||||
val companyIdentifier = __pigeon_list[0].let { num -> if (num is Int) num.toLong() else num as Long }
|
||||
val data = __pigeon_list[1] as ByteArray?
|
||||
val mask = __pigeon_list[2] as ByteArray?
|
||||
return UniversalManufacturerDataFilter(companyIdentifier, data, mask)
|
||||
@@ -178,6 +178,28 @@ data class UniversalManufacturerDataFilter (
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated class from Pigeon that represents data sent in messages. */
|
||||
data class UniversalManufacturerData (
|
||||
val companyIdentifier: Long,
|
||||
val data: ByteArray
|
||||
|
||||
) {
|
||||
companion object {
|
||||
@Suppress("LocalVariableName")
|
||||
fun fromList(__pigeon_list: List<Any?>): UniversalManufacturerData {
|
||||
val companyIdentifier = __pigeon_list[0].let { num -> if (num is Int) num.toLong() else num as Long }
|
||||
val data = __pigeon_list[1] as ByteArray
|
||||
return UniversalManufacturerData(companyIdentifier, data)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
return listOf(
|
||||
companyIdentifier,
|
||||
data,
|
||||
)
|
||||
}
|
||||
}
|
||||
private object UniversalBlePigeonCodec : StandardMessageCodec() {
|
||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||
return when (type) {
|
||||
@@ -206,6 +228,11 @@ private object UniversalBlePigeonCodec : StandardMessageCodec() {
|
||||
UniversalManufacturerDataFilter.fromList(it)
|
||||
}
|
||||
}
|
||||
134.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
UniversalManufacturerData.fromList(it)
|
||||
}
|
||||
}
|
||||
else -> super.readValueOfType(type, buffer)
|
||||
}
|
||||
}
|
||||
@@ -231,6 +258,10 @@ private object UniversalBlePigeonCodec : StandardMessageCodec() {
|
||||
stream.write(133)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is UniversalManufacturerData -> {
|
||||
stream.write(134)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
else -> super.writeValue(stream, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.navideck.universal_ble
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.le.ScanFilter
|
||||
import android.bluetooth.le.ScanResult
|
||||
import android.os.ParcelUuid
|
||||
import android.util.Log
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.util.UUID
|
||||
import kotlin.experimental.and
|
||||
|
||||
private const val TAG = "UniversalBlePlugin"
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
class UniversalBleFilterUtil {
|
||||
var scanFilter: UniversalScanFilter? = null
|
||||
var serviceFilterUUIDS: List<UUID> = emptyList()
|
||||
|
||||
fun filterDevice(
|
||||
name: String?,
|
||||
manufacturerDataList: List<UniversalManufacturerData>,
|
||||
serviceUuids: Array<UUID>,
|
||||
): Boolean {
|
||||
val filter = scanFilter ?: return true
|
||||
|
||||
val hasNamePrefixFilter = filter.withNamePrefix.isNotEmpty()
|
||||
val hasServiceFilter = filter.withServices.isNotEmpty()
|
||||
val hasManufacturerDataFilter = filter.withManufacturerData.isNotEmpty()
|
||||
|
||||
// If there is no filter at all, then allow device
|
||||
if (!hasNamePrefixFilter &&
|
||||
!hasServiceFilter &&
|
||||
!hasManufacturerDataFilter
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// For not, we only have DeviceName filter
|
||||
return hasNamePrefixFilter && isNameMatchingFilters(filter, name) ||
|
||||
hasServiceFilter && isServicesMatchingFilters(serviceUuids) ||
|
||||
hasManufacturerDataFilter && isManufacturerDataMatchingFilters(
|
||||
filter,
|
||||
manufacturerDataList
|
||||
)
|
||||
}
|
||||
|
||||
private fun isNameMatchingFilters(scanFilter: UniversalScanFilter, name: String?): Boolean {
|
||||
val namePrefixFilter = scanFilter.withNamePrefix.filterNotNull()
|
||||
if (namePrefixFilter.isEmpty()) {
|
||||
return true
|
||||
}
|
||||
if (name.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
return namePrefixFilter.any { name.startsWith(it) }
|
||||
}
|
||||
|
||||
private fun isServicesMatchingFilters(
|
||||
serviceUuids: Array<UUID>,
|
||||
): Boolean {
|
||||
if (serviceFilterUUIDS.isEmpty()) {
|
||||
return true
|
||||
}
|
||||
if (serviceUuids.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
return serviceFilterUUIDS.any {
|
||||
serviceUuids.contains(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isManufacturerDataMatchingFilters(
|
||||
scanFilter: UniversalScanFilter,
|
||||
manufacturerDataList: List<UniversalManufacturerData>,
|
||||
): Boolean {
|
||||
val filters = scanFilter.withManufacturerData.filterNotNull()
|
||||
if (filters.isEmpty()) return true
|
||||
if (manufacturerDataList.isEmpty()) return false
|
||||
return manufacturerDataList.any { mfd ->
|
||||
filters.any { filter ->
|
||||
mfd.companyIdentifier == filter.companyIdentifier &&
|
||||
isDataMatching(filter.data, mfd.data, filter.mask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDataMatching(
|
||||
filterData: ByteArray?,
|
||||
deviceData: ByteArray,
|
||||
filterMask: ByteArray?,
|
||||
): Boolean {
|
||||
if (filterData == null) return true
|
||||
if (filterData.size > deviceData.size) return false
|
||||
|
||||
val mask = filterMask ?: ByteArray(filterData.size) { 0xFF.toByte() }
|
||||
if (filterData.size != mask.size) return false
|
||||
|
||||
return filterData.indices.all { i ->
|
||||
(filterData[i] and mask[i]) == (deviceData[i] and mask[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun UniversalScanFilter.hasCustomFilter(): Boolean {
|
||||
// Only NamePrefix Filtering is not allowed in native filters
|
||||
return withNamePrefix.isNotEmpty()
|
||||
}
|
||||
|
||||
// Convert UniversalScanFilter to ScanFilter
|
||||
fun UniversalScanFilter.toScanFilters(serviceUuids: List<UUID>): List<ScanFilter> {
|
||||
val scanFilters: ArrayList<ScanFilter> = arrayListOf()
|
||||
|
||||
// Add withServices Filter
|
||||
for (service in serviceUuids) {
|
||||
try {
|
||||
service.let {
|
||||
scanFilters.add(
|
||||
ScanFilter.Builder().setServiceUuid(ParcelUuid(it)).build()
|
||||
)
|
||||
Log.e(TAG, "scanFilters: $it")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, e.toString())
|
||||
throw FlutterError(
|
||||
"illegalIllegalArgument",
|
||||
"Invalid serviceId: $service",
|
||||
e.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
@@ -18,6 +18,8 @@ import android.bluetooth.le.ScanResult
|
||||
import android.os.Build
|
||||
import android.os.ParcelUuid
|
||||
import android.util.Log
|
||||
import android.util.SparseArray
|
||||
import androidx.core.util.keyIterator
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.util.UUID
|
||||
@@ -85,6 +87,10 @@ fun String.validFullUUID(): String {
|
||||
}
|
||||
}
|
||||
|
||||
fun List<String>.toUUIDList(): List<UUID> {
|
||||
return this.map { UUID.fromString(it.validFullUUID()) }
|
||||
}
|
||||
|
||||
fun String.toBluetoothGatt(): BluetoothGatt {
|
||||
return knownGatts.find { it.device.address == this }
|
||||
?: throw FlutterError("IllegalArgument", "Unknown deviceId: $this", null)
|
||||
@@ -156,70 +162,19 @@ fun Int.parseGattErrorCode(): String? {
|
||||
}
|
||||
}
|
||||
|
||||
fun UniversalScanFilter.toScanFilters(): List<ScanFilter> {
|
||||
val scanFilters: ArrayList<ScanFilter> = arrayListOf()
|
||||
// Add withServices Filter
|
||||
for (service in this.withServices) {
|
||||
try {
|
||||
val serviceUUID = service?.validFullUUID()
|
||||
serviceUUID?.let {
|
||||
val parcelUUId = ParcelUuid.fromString(it)
|
||||
scanFilters.add(
|
||||
ScanFilter.Builder().setServiceUuid(parcelUUId).build()
|
||||
)
|
||||
Log.e(TAG, "scanFilters: $parcelUUId")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, e.toString())
|
||||
throw FlutterError(
|
||||
"illegalIllegalArgument",
|
||||
"Invalid serviceId: $service",
|
||||
e.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 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?
|
||||
val ScanResult.manufacturerDataList: List<UniversalManufacturerData>
|
||||
get() {
|
||||
val sparseArray = scanRecord?.manufacturerSpecificData ?: return null
|
||||
if (sparseArray.size() == 0) return null
|
||||
|
||||
return sparseArray.keyAt(0).toShort().toByteArray() + sparseArray.valueAt(0)
|
||||
return scanRecord?.manufacturerSpecificData?.toList()?.map { (key, value) ->
|
||||
UniversalManufacturerData(key.toLong(), value)
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
fun <T> SparseArray<T>.toList(): List<Pair<Int, T>> {
|
||||
return (0 until size()).map { index ->
|
||||
keyAt(index) to valueAt(index)
|
||||
}
|
||||
}
|
||||
|
||||
fun BluetoothGatt.getCharacteristic(
|
||||
service: String,
|
||||
characteristic: String,
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
|
||||
import io.flutter.plugin.common.*
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -38,6 +39,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
private lateinit var bluetoothManager: BluetoothManager
|
||||
private val cachedServicesMap = mutableMapOf<String, List<String>>()
|
||||
private val devicesStateMap = mutableMapOf<String, Int>()
|
||||
private val universalBleFilterUtil = UniversalBleFilterUtil()
|
||||
|
||||
// Flutter Futures
|
||||
private var bluetoothEnableRequestFuture: ((Result<Boolean>) -> Unit)? = null
|
||||
@@ -114,11 +116,32 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
}
|
||||
val settings = builder.build()
|
||||
|
||||
bluetoothManager.adapter.bluetoothLeScanner?.startScan(
|
||||
filter?.toScanFilters() ?: emptyList<ScanFilter>(),
|
||||
settings,
|
||||
scanCallback
|
||||
)
|
||||
val hasCustomFilters = filter?.hasCustomFilter() ?: false;
|
||||
|
||||
try {
|
||||
val filterServices = filter?.withServices?.filterNotNull()?.toUUIDList() ?: emptyList()
|
||||
var scanFilters = emptyList<ScanFilter>()
|
||||
|
||||
// Set custom scan filter only if required
|
||||
if (hasCustomFilters) {
|
||||
Log.e(TAG, "Using Custom Filters")
|
||||
universalBleFilterUtil.scanFilter = filter
|
||||
universalBleFilterUtil.serviceFilterUUIDS = filterServices
|
||||
} else {
|
||||
universalBleFilterUtil.scanFilter = null
|
||||
scanFilters = filter?.toScanFilters(filterServices) ?: emptyList<ScanFilter>()
|
||||
}
|
||||
|
||||
bluetoothManager.adapter.bluetoothLeScanner?.startScan(
|
||||
scanFilters, settings, scanCallback
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
throw FlutterError(
|
||||
"illegalIllegalArgument",
|
||||
"Failed to start Scan",
|
||||
e.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopScan() {
|
||||
@@ -573,8 +596,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
name = it.name,
|
||||
deviceId = it.address,
|
||||
isPaired = it.bondState == BOND_BONDED,
|
||||
manufacturerDataHead = null,
|
||||
manufacturerData = null,
|
||||
manufacturerDataList = null,
|
||||
rssi = null,
|
||||
)
|
||||
}
|
||||
@@ -790,31 +812,45 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val scanCallback = object : ScanCallback() {
|
||||
override fun onScanFailed(errorCode: Int) {
|
||||
Log.e(TAG, "OnScanFailed: ${errorCode.parseScanErrorMessage()}")
|
||||
}
|
||||
|
||||
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||
|
||||
// Log.v(TAG, "onScanResult: $result")
|
||||
var serviceUuids: Array<String> = arrayOf()
|
||||
var serviceUuids: Array<UUID> = arrayOf()
|
||||
result.device.uuids?.forEach {
|
||||
serviceUuids += it.uuid.toString()
|
||||
serviceUuids += it.uuid
|
||||
}
|
||||
result.scanRecord?.serviceUuids?.forEach {
|
||||
if (!serviceUuids.contains(it.uuid.toString())) {
|
||||
serviceUuids += it.uuid.toString()
|
||||
if (!serviceUuids.contains(it.uuid)) {
|
||||
serviceUuids += it.uuid
|
||||
}
|
||||
}
|
||||
|
||||
val name = result.device.name
|
||||
val manufacturerDataList = result.manufacturerDataList
|
||||
|
||||
if (!universalBleFilterUtil.filterDevice(
|
||||
name,
|
||||
manufacturerDataList,
|
||||
serviceUuids
|
||||
)
|
||||
) return
|
||||
|
||||
|
||||
mainThreadHandler?.post {
|
||||
callbackChannel?.onScanResult(
|
||||
UniversalBleScanResult(
|
||||
name = result.device.name,
|
||||
deviceId = result.device.address,
|
||||
isPaired = result.device.bondState == BOND_BONDED,
|
||||
manufacturerDataHead = result.manufacturerDataHead,
|
||||
manufacturerDataList = manufacturerDataList,
|
||||
rssi = result.rssi.toLong(),
|
||||
services = serviceUuids.toList()
|
||||
services = serviceUuids.map { it.toString() }.toList()
|
||||
)
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,7 @@ struct UniversalBleScanResult {
|
||||
var name: String? = nil
|
||||
var isPaired: Bool? = nil
|
||||
var rssi: Int64? = nil
|
||||
var manufacturerData: FlutterStandardTypedData? = nil
|
||||
var manufacturerDataHead: FlutterStandardTypedData? = nil
|
||||
var manufacturerDataList: [UniversalManufacturerData?]? = nil
|
||||
var services: [String?]? = nil
|
||||
|
||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||
@@ -84,17 +83,15 @@ struct UniversalBleScanResult {
|
||||
let name: String? = nilOrValue(__pigeon_list[1])
|
||||
let isPaired: Bool? = nilOrValue(__pigeon_list[2])
|
||||
let rssi: Int64? = isNullish(__pigeon_list[3]) ? nil : (__pigeon_list[3] is Int64? ? __pigeon_list[3] as! Int64? : Int64(__pigeon_list[3] as! Int32))
|
||||
let manufacturerData: FlutterStandardTypedData? = nilOrValue(__pigeon_list[4])
|
||||
let manufacturerDataHead: FlutterStandardTypedData? = nilOrValue(__pigeon_list[5])
|
||||
let services: [String?]? = nilOrValue(__pigeon_list[6])
|
||||
let manufacturerDataList: [UniversalManufacturerData?]? = nilOrValue(__pigeon_list[4])
|
||||
let services: [String?]? = nilOrValue(__pigeon_list[5])
|
||||
|
||||
return UniversalBleScanResult(
|
||||
deviceId: deviceId,
|
||||
name: name,
|
||||
isPaired: isPaired,
|
||||
rssi: rssi,
|
||||
manufacturerData: manufacturerData,
|
||||
manufacturerDataHead: manufacturerDataHead,
|
||||
manufacturerDataList: manufacturerDataList,
|
||||
services: services
|
||||
)
|
||||
}
|
||||
@@ -104,8 +101,7 @@ struct UniversalBleScanResult {
|
||||
name,
|
||||
isPaired,
|
||||
rssi,
|
||||
manufacturerData,
|
||||
manufacturerDataHead,
|
||||
manufacturerDataList,
|
||||
services,
|
||||
]
|
||||
}
|
||||
@@ -162,21 +158,25 @@ struct UniversalBleCharacteristic {
|
||||
/// Generated class from Pigeon that represents data sent in messages.
|
||||
struct UniversalScanFilter {
|
||||
var withServices: [String?]
|
||||
var withNamePrefix: [String?]
|
||||
var withManufacturerData: [UniversalManufacturerDataFilter?]
|
||||
|
||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||
static func fromList(_ __pigeon_list: [Any?]) -> UniversalScanFilter? {
|
||||
let withServices = __pigeon_list[0] as! [String?]
|
||||
let withManufacturerData = __pigeon_list[1] as! [UniversalManufacturerDataFilter?]
|
||||
let withNamePrefix = __pigeon_list[1] as! [String?]
|
||||
let withManufacturerData = __pigeon_list[2] as! [UniversalManufacturerDataFilter?]
|
||||
|
||||
return UniversalScanFilter(
|
||||
withServices: withServices,
|
||||
withNamePrefix: withNamePrefix,
|
||||
withManufacturerData: withManufacturerData
|
||||
)
|
||||
}
|
||||
func toList() -> [Any?] {
|
||||
return [
|
||||
withServices,
|
||||
withNamePrefix,
|
||||
withManufacturerData,
|
||||
]
|
||||
}
|
||||
@@ -184,13 +184,13 @@ struct UniversalScanFilter {
|
||||
|
||||
/// Generated class from Pigeon that represents data sent in messages.
|
||||
struct UniversalManufacturerDataFilter {
|
||||
var companyIdentifier: Int64? = nil
|
||||
var companyIdentifier: Int64
|
||||
var data: FlutterStandardTypedData? = nil
|
||||
var mask: FlutterStandardTypedData? = nil
|
||||
|
||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||
static func fromList(_ __pigeon_list: [Any?]) -> UniversalManufacturerDataFilter? {
|
||||
let companyIdentifier: Int64? = isNullish(__pigeon_list[0]) ? nil : (__pigeon_list[0] is Int64? ? __pigeon_list[0] as! Int64? : Int64(__pigeon_list[0] as! Int32))
|
||||
let companyIdentifier = __pigeon_list[0] is Int64 ? __pigeon_list[0] as! Int64 : Int64(__pigeon_list[0] as! Int32)
|
||||
let data: FlutterStandardTypedData? = nilOrValue(__pigeon_list[1])
|
||||
let mask: FlutterStandardTypedData? = nilOrValue(__pigeon_list[2])
|
||||
|
||||
@@ -208,6 +208,29 @@ struct UniversalManufacturerDataFilter {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Generated class from Pigeon that represents data sent in messages.
|
||||
struct UniversalManufacturerData {
|
||||
var companyIdentifier: Int64
|
||||
var data: FlutterStandardTypedData
|
||||
|
||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||
static func fromList(_ __pigeon_list: [Any?]) -> UniversalManufacturerData? {
|
||||
let companyIdentifier = __pigeon_list[0] is Int64 ? __pigeon_list[0] as! Int64 : Int64(__pigeon_list[0] as! Int32)
|
||||
let data = __pigeon_list[1] as! FlutterStandardTypedData
|
||||
|
||||
return UniversalManufacturerData(
|
||||
companyIdentifier: companyIdentifier,
|
||||
data: data
|
||||
)
|
||||
}
|
||||
func toList() -> [Any?] {
|
||||
return [
|
||||
companyIdentifier,
|
||||
data,
|
||||
]
|
||||
}
|
||||
}
|
||||
private class UniversalBlePigeonCodecReader: FlutterStandardReader {
|
||||
override func readValue(ofType type: UInt8) -> Any? {
|
||||
switch type {
|
||||
@@ -221,6 +244,8 @@ private class UniversalBlePigeonCodecReader: FlutterStandardReader {
|
||||
return UniversalScanFilter.fromList(self.readValue() as! [Any?])
|
||||
case 133:
|
||||
return UniversalManufacturerDataFilter.fromList(self.readValue() as! [Any?])
|
||||
case 134:
|
||||
return UniversalManufacturerData.fromList(self.readValue() as! [Any?])
|
||||
default:
|
||||
return super.readValue(ofType: type)
|
||||
}
|
||||
@@ -244,6 +269,9 @@ private class UniversalBlePigeonCodecWriter: FlutterStandardWriter {
|
||||
} else if let value = value as? UniversalManufacturerDataFilter {
|
||||
super.writeByte(133)
|
||||
super.writeValue(value.toList())
|
||||
} else if let value = value as? UniversalManufacturerData {
|
||||
super.writeByte(134)
|
||||
super.writeValue(value.toList())
|
||||
} else {
|
||||
super.writeValue(value)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// UniversalBleFilterUtil.swift
|
||||
// universal_ble
|
||||
//
|
||||
// Created by Rohit Sangwan on 23/08/24.
|
||||
//
|
||||
|
||||
import CoreBluetooth
|
||||
import Foundation
|
||||
|
||||
public class UniversalBleFilterUtil {
|
||||
var scanFilter: UniversalScanFilter?
|
||||
var scanFilterServicesUUID: [CBUUID] = []
|
||||
|
||||
func filterDevice(name: String?, manufacturerData: UniversalManufacturerData?, services: [CBUUID]?) -> Bool {
|
||||
guard let filter = scanFilter else {
|
||||
return true
|
||||
}
|
||||
|
||||
let hasNamePrefixFilter = !filter.withNamePrefix.isEmpty
|
||||
let hasServiceFilter = !filter.withServices.isEmpty
|
||||
let hasManufacturerDataFilter = !filter.withManufacturerData.isEmpty
|
||||
|
||||
// If there is no filter at all, then allow device
|
||||
if !hasNamePrefixFilter && !hasServiceFilter && !hasManufacturerDataFilter {
|
||||
return true
|
||||
}
|
||||
|
||||
// Else check one of the filter passes
|
||||
return hasNamePrefixFilter && isNameMatchingFilters(filter: filter, name: name) ||
|
||||
hasServiceFilter && isServicesMatchingFilters(services: services) ||
|
||||
hasManufacturerDataFilter && isManufacturerDataMatchingFilters(scanFilter: filter, msd: manufacturerData)
|
||||
}
|
||||
|
||||
func isNameMatchingFilters(filter: UniversalScanFilter, name: String?) -> Bool {
|
||||
let prefixFilters = filter.withNamePrefix.compactMap { $0 }.filter { !$0.isEmpty }
|
||||
|
||||
guard !prefixFilters.isEmpty else {
|
||||
return true
|
||||
}
|
||||
|
||||
guard let name = name, !name.isEmpty else {
|
||||
return false
|
||||
}
|
||||
|
||||
return prefixFilters.contains { name.hasPrefix($0) }
|
||||
}
|
||||
|
||||
func isServicesMatchingFilters(services: [CBUUID]?) -> Bool {
|
||||
let serviceFilters = Set(scanFilterServicesUUID.compactMap { $0 })
|
||||
|
||||
guard !serviceFilters.isEmpty else {
|
||||
return true
|
||||
}
|
||||
|
||||
guard let services = services, !services.isEmpty else {
|
||||
return false
|
||||
}
|
||||
|
||||
return !Set(services).isDisjoint(with: serviceFilters)
|
||||
}
|
||||
|
||||
func isManufacturerDataMatchingFilters(scanFilter: UniversalScanFilter, msd: UniversalManufacturerData?) -> Bool {
|
||||
let filters = scanFilter.withManufacturerData.compactMap { $0 }
|
||||
if filters.isEmpty {
|
||||
return true
|
||||
}
|
||||
|
||||
guard let msd = msd else {
|
||||
return false
|
||||
}
|
||||
|
||||
for filter in filters {
|
||||
let companyIdentifier: Int64 = filter.companyIdentifier
|
||||
if msd.companyIdentifier == companyIdentifier && findData(find: filter.data?.toData(), inData: msd.data.toData(), 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
|
||||
}
|
||||
}
|
||||
|
||||
extension UniversalScanFilter {
|
||||
var hasCustomFilters: Bool {
|
||||
return !withManufacturerData.isEmpty || !withNamePrefix.isEmpty
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ extension String {
|
||||
let baseUuid = "00000000-0000-1000-8000-00805F9B34FB"
|
||||
let start = baseUuid.startIndex
|
||||
let range = baseUuid.index(start, offsetBy: 4 - uuidLength) ..< baseUuid.index(start, offsetBy: 4)
|
||||
return baseUuid.replacingCharacters(in: range, with: self)
|
||||
return baseUuid.replacingCharacters(in: range, with: self).lowercased()
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
@@ -163,43 +163,6 @@ extension FlutterStandardTypedData {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -26,8 +26,8 @@ private var discoveredPeripherals = [String: CBPeripheral]()
|
||||
|
||||
private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentralManagerDelegate, CBPeripheralDelegate {
|
||||
var callbackChannel: UniversalBleCallbackChannel
|
||||
private var universalBleFilterUtil = UniversalBleFilterUtil()
|
||||
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]()
|
||||
@@ -59,19 +59,21 @@ 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 {
|
||||
throw FlutterError(code: "IllegalArgument", message: "Invalid service UUID:\(service)", details: nil)
|
||||
}
|
||||
withServices.append(CBUUID(string: service))
|
||||
}
|
||||
}
|
||||
// If filter have any other filter other then official one
|
||||
let hasCustomFilter = filter?.hasCustomFilters ?? false
|
||||
|
||||
// Save scanFilter for later user
|
||||
scanFilter = filter
|
||||
// Apply services filter
|
||||
var withServices: [CBUUID] = try filter?.withServices.compactMap { $0 }.toCBUUID() ?? []
|
||||
|
||||
if hasCustomFilter {
|
||||
print("Using Custom Filters")
|
||||
universalBleFilterUtil.scanFilter = filter
|
||||
universalBleFilterUtil.scanFilterServicesUUID = withServices
|
||||
withServices = []
|
||||
} else {
|
||||
universalBleFilterUtil.scanFilter = nil
|
||||
universalBleFilterUtil.scanFilterServicesUUID = []
|
||||
}
|
||||
|
||||
let options = [CBCentralManagerScanOptionAllowDuplicatesKey: true]
|
||||
manager.scanForPeripherals(withServices: withServices, options: options)
|
||||
@@ -280,7 +282,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
|
||||
completion(Result.failure(FlutterError(code: "NotSupported", message: nil, details: nil)))
|
||||
}
|
||||
|
||||
func pair(deviceId _: String, completion: @escaping (Result<Bool, Error>) -> Void){
|
||||
func pair(deviceId _: String, completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||
completion(Result.failure(FlutterError(code: "Implemented in Dart", message: nil, details: nil)))
|
||||
}
|
||||
|
||||
@@ -310,17 +312,26 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
|
||||
}
|
||||
|
||||
public func centralManager(_: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
||||
// Store the discovered peripheral using its UUID as the key
|
||||
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
|
||||
}
|
||||
// Extract manufacturer data and service UUIDs from the advertisement data
|
||||
let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
|
||||
let services = (advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID])
|
||||
|
||||
var manufacturerDataList: [UniversalManufacturerData] = []
|
||||
var universalManufacturerData: UniversalManufacturerData? = nil
|
||||
|
||||
if let msd = manufacturerData, msd.count > 2 {
|
||||
let companyIdentifier = msd.prefix(2).withUnsafeBytes { $0.load(as: UInt16.self) }
|
||||
let data = FlutterStandardTypedData(bytes: msd.suffix(from: 2))
|
||||
universalManufacturerData = UniversalManufacturerData(companyIdentifier: Int64(companyIdentifier), data: data)
|
||||
manufacturerDataList.append(universalManufacturerData!)
|
||||
}
|
||||
|
||||
// Apply custom filters and return early if the peripheral doesn't match
|
||||
if !universalBleFilterUtil.filterDevice(name: peripheral.name, manufacturerData: universalManufacturerData, services: services) {
|
||||
return
|
||||
}
|
||||
|
||||
callbackChannel.onScanResult(result: UniversalBleScanResult(
|
||||
@@ -328,8 +339,8 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
|
||||
name: peripheral.name,
|
||||
isPaired: nil,
|
||||
rssi: RSSI as? Int64,
|
||||
manufacturerData: FlutterStandardTypedData(bytes: manufacturerData ?? Data()),
|
||||
services: services?.map { $0.uuidStr }
|
||||
manufacturerDataList: manufacturerDataList,
|
||||
services: services?.map { $0.uuidStr.validFullUUID }
|
||||
)) { _ in }
|
||||
}
|
||||
|
||||
@@ -451,4 +462,15 @@ extension String {
|
||||
}
|
||||
}
|
||||
|
||||
extension [String] {
|
||||
func toCBUUID() throws -> [CBUUID] {
|
||||
return try compactMap { serviceUUID in
|
||||
guard UUID(uuidString: serviceUUID.validFullUUID) != nil else {
|
||||
throw FlutterError(code: "IllegalArgument", message: "Invalid service UUID:\(serviceUUID)", details: nil)
|
||||
}
|
||||
return CBUUID(string: serviceUUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FlutterError: Error {}
|
||||
|
||||
@@ -9,7 +9,7 @@ class MockUniversalBle extends UniversalBlePlatform {
|
||||
name: 'MockDevice',
|
||||
deviceId: 'MockDeviceId',
|
||||
rssi: 50,
|
||||
manufacturerData: Uint8List(0),
|
||||
manufacturerDataList: [],
|
||||
);
|
||||
|
||||
Uint8List _serviceValue = utf8.encode('Result');
|
||||
|
||||
+10
-16
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
import 'package:universal_ble_example/data/mock_universal_ble.dart';
|
||||
@@ -92,6 +90,12 @@ class _MyAppState extends State<MyApp> {
|
||||
);
|
||||
}
|
||||
|
||||
void showSnackbar(message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(message.toString())),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -130,9 +134,7 @@ class _MyAppState extends State<MyApp> {
|
||||
setState(() {
|
||||
_isScanning = false;
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(e.toString())),
|
||||
);
|
||||
showSnackbar(e);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -151,9 +153,7 @@ class _MyAppState extends State<MyApp> {
|
||||
text: 'Enable Bluetooth',
|
||||
onPressed: () async {
|
||||
bool isEnabled = await UniversalBle.enableBluetooth();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("BluetoothEnabled: $isEnabled")),
|
||||
);
|
||||
showSnackbar("BluetoothEnabled: $isEnabled");
|
||||
},
|
||||
),
|
||||
if (BleCapabilities.requiresRuntimePermission)
|
||||
@@ -163,9 +163,7 @@ class _MyAppState extends State<MyApp> {
|
||||
bool hasPermissions =
|
||||
await PermissionHandler.arePermissionsGranted();
|
||||
if (hasPermissions) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("Permissions granted")),
|
||||
);
|
||||
showSnackbar("Permissions granted");
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -176,11 +174,7 @@ class _MyAppState extends State<MyApp> {
|
||||
List<BleDevice> devices =
|
||||
await UniversalBle.getSystemDevices();
|
||||
if (devices.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("No Connected Devices Found"),
|
||||
),
|
||||
);
|
||||
showSnackbar("No Connected Devices Found");
|
||||
}
|
||||
setState(() {
|
||||
_bleDevices.clear();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
@@ -10,10 +9,10 @@ class ScannedItemWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String? name = bleDevice.name;
|
||||
Uint8List? rawManufacturerData = bleDevice.manufacturerData;
|
||||
List<ManufacturerData> rawManufacturerData = bleDevice.manufacturerDataList;
|
||||
ManufacturerData? manufacturerData;
|
||||
if (rawManufacturerData != null && rawManufacturerData.isNotEmpty) {
|
||||
manufacturerData = ManufacturerData.fromData(rawManufacturerData);
|
||||
if (rawManufacturerData.isNotEmpty) {
|
||||
manufacturerData = rawManufacturerData.first;
|
||||
}
|
||||
if (name == null || name.isEmpty) name = 'NA';
|
||||
return Padding(
|
||||
@@ -29,9 +28,7 @@ class ScannedItemWidget extends StatelessWidget {
|
||||
Text(bleDevice.deviceId),
|
||||
Visibility(
|
||||
visible: manufacturerData != null,
|
||||
child: Text(
|
||||
'CompanyIdentifier: ${manufacturerData.toString()} (${manufacturerData?.companyIdRadix16})',
|
||||
),
|
||||
child: Text(manufacturerData.toString()),
|
||||
),
|
||||
bleDevice.isPaired == true
|
||||
? const Text(
|
||||
|
||||
@@ -98,13 +98,13 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
||||
if (kIsWeb) {
|
||||
_addLog(
|
||||
"DiscoverServices",
|
||||
'${services.length} services discovered,\nNote: Only services added in ScanFilter or WebConfig will be discovered',
|
||||
'${services.length} services discovered,\nNote: Only services added in ScanFilter or WebOptions will be discovered',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
_addLog(
|
||||
"DiscoverServicesError",
|
||||
'$e\nNote: Only services added in ScanFilter or WebConfig will be discovered',
|
||||
'$e\nNote: Only services added in ScanFilter or WebOptions will be discovered',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ packages:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.12.0"
|
||||
version: "0.13.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -415,10 +415,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.4"
|
||||
version: "14.2.5"
|
||||
webdriver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -9,8 +9,12 @@ class BleDevice {
|
||||
bool? isPaired;
|
||||
List<String> services;
|
||||
bool? isSystemDevice;
|
||||
Uint8List? manufacturerDataHead;
|
||||
Uint8List? manufacturerData;
|
||||
List<ManufacturerData> manufacturerDataList;
|
||||
|
||||
@Deprecated("Use `manufacturerDataList` instead")
|
||||
Uint8List? get manufacturerData => manufacturerDataList.isEmpty
|
||||
? null
|
||||
: manufacturerDataList.first.toUint8List();
|
||||
|
||||
/// Returns connection state of the device.
|
||||
/// All platforms will return `Connected/Disconnected` states.
|
||||
@@ -30,12 +34,8 @@ class BleDevice {
|
||||
this.isPaired,
|
||||
this.services = const [],
|
||||
this.isSystemDevice,
|
||||
Uint8List? manufacturerData,
|
||||
Uint8List? manufacturerDataHead,
|
||||
}) {
|
||||
this.manufacturerDataHead = manufacturerDataHead ?? Uint8List.fromList([]);
|
||||
this.manufacturerData = manufacturerData ?? manufacturerDataHead;
|
||||
}
|
||||
this.manufacturerDataList = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@@ -46,34 +46,6 @@ class BleDevice {
|
||||
'isPaired: $isPaired, '
|
||||
'services: $services, '
|
||||
'isSystemDevice: $isSystemDevice, '
|
||||
'manufacturerDataHead: $manufacturerDataHead, '
|
||||
'manufacturerData: $manufacturerData';
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the manufacturer data of a BLE device.
|
||||
/// Use [BleDevice.manufacturerData] with [ManufacturerData.fromData] to create an instance of this class.
|
||||
class ManufacturerData {
|
||||
final int? companyId;
|
||||
final Uint8List? data;
|
||||
String? companyIdRadix16;
|
||||
ManufacturerData(this.companyId, this.data) {
|
||||
if (companyId != null) {
|
||||
companyIdRadix16 = "0x0${companyId!.toRadixString(16)}";
|
||||
}
|
||||
}
|
||||
|
||||
factory ManufacturerData.fromData(Uint8List data) {
|
||||
if (data.length < 2) return ManufacturerData(null, data);
|
||||
int manufacturerIdInt = (data[0] + (data[1] << 8));
|
||||
return ManufacturerData(
|
||||
manufacturerIdInt,
|
||||
data.sublist(2),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ManufacturerData: companyId: $companyIdRadix16, data: $data';
|
||||
'manufacturerDataList: $manufacturerDataList';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Represents the manufacturer data of a BLE device.
|
||||
class ManufacturerData {
|
||||
final int companyId;
|
||||
final Uint8List payload;
|
||||
ManufacturerData(this.companyId, this.payload);
|
||||
|
||||
String get companyIdRadix16 => "0x0${companyId.toRadixString(16)}";
|
||||
|
||||
factory ManufacturerData.fromData(Uint8List data) {
|
||||
if (data.length < 2) {
|
||||
throw const FormatException("Invalid Manufacturer Data");
|
||||
}
|
||||
return ManufacturerData(
|
||||
(data[0] + (data[1] << 8)),
|
||||
data.sublist(2),
|
||||
);
|
||||
}
|
||||
|
||||
Uint8List toUint8List() {
|
||||
final byteData = ByteData(2);
|
||||
byteData.setInt16(0, companyId, Endian.host);
|
||||
return Uint8List.fromList(
|
||||
byteData.buffer.asUint8List() + payload.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => companyId.hashCode ^ payload.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is! ManufacturerData) return false;
|
||||
return companyId == other.companyId && listEquals(payload, other.payload);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Manufacturer: $companyIdRadix16 - $payload';
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export 'package:universal_ble/src/models/manufacturer_data.dart';
|
||||
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';
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
/// Platform specific config to scan devices
|
||||
class PlatformConfig {
|
||||
WebConfig? web;
|
||||
WebOptions? web;
|
||||
|
||||
PlatformConfig({this.web});
|
||||
}
|
||||
|
||||
/// Web config to scan devices
|
||||
/// Web options to scan devices
|
||||
/// [optionalServices] is a list of service uuid's to ensure that you can access the specified services after connecting to the device,
|
||||
/// by default services from scanFilter will be used
|
||||
/// [optionalManufacturerData] is list of `CompanyIdentifier's` and used to add `ManufacturerData` in advertisement results of selected device from web dialog,
|
||||
/// by default manufacturerData from scanFilter will be used
|
||||
/// Checkout more details on [web](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/requestDevice)
|
||||
/// Note: you will only get advertisements if Experimental Flag is enabled in the browser
|
||||
class WebConfig {
|
||||
class WebOptions {
|
||||
List<String> optionalServices;
|
||||
List<int> optionalManufacturerData;
|
||||
|
||||
WebConfig({
|
||||
WebOptions({
|
||||
this.optionalServices = const [],
|
||||
this.optionalManufacturerData = const [],
|
||||
});
|
||||
|
||||
@@ -18,10 +18,10 @@ class ScanFilter {
|
||||
}
|
||||
|
||||
class ManufacturerDataFilter {
|
||||
int? companyIdentifier;
|
||||
int companyIdentifier;
|
||||
|
||||
// Mask and data must be of same length
|
||||
Uint8List? data;
|
||||
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.
|
||||
@@ -29,13 +29,13 @@ class ManufacturerDataFilter {
|
||||
Uint8List? mask;
|
||||
|
||||
ManufacturerDataFilter({
|
||||
this.companyIdentifier,
|
||||
this.data,
|
||||
required this.companyIdentifier,
|
||||
this.payload,
|
||||
this.mask,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ManufacturerDataFilter(companyIdentifier: $companyIdentifier, data: $data, mask: $mask)';
|
||||
return 'ManufacturerDataFilter(companyIdentifier: $companyIdentifier, payload: $payload, mask: $mask)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
// A default Filter on dart side
|
||||
// Used on Linux only
|
||||
class UniversalBleFilterUtil {
|
||||
ScanFilter? scanFilter;
|
||||
|
||||
bool filterDevice(BleDevice device) {
|
||||
final filter = scanFilter;
|
||||
if (filter == null) return true;
|
||||
|
||||
final hasNamePrefixFilter = filter.withNamePrefix.isNotEmpty;
|
||||
final hasServiceFilter = filter.withServices.isNotEmpty;
|
||||
final hasManufacturerDataFilter = filter.withManufacturerData.isNotEmpty;
|
||||
|
||||
// If there is no filter at all, then allow device
|
||||
if (!hasNamePrefixFilter &&
|
||||
!hasServiceFilter &&
|
||||
!hasManufacturerDataFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Else check one of the filter passes
|
||||
return hasNamePrefixFilter && isNameMatchingFilters(filter, device) ||
|
||||
hasServiceFilter && isServicesMatchingFilters(filter, device) ||
|
||||
hasManufacturerDataFilter &&
|
||||
isManufacturerDataMatchingFilters(filter, device);
|
||||
}
|
||||
|
||||
bool isNameMatchingFilters(ScanFilter scanFilter, BleDevice device) {
|
||||
var namePrefixFilter = scanFilter.withNamePrefix;
|
||||
if (namePrefixFilter.isEmpty) return true;
|
||||
|
||||
String? name = device.name;
|
||||
if (name == null || name.isEmpty) return false;
|
||||
return namePrefixFilter.any(name.startsWith);
|
||||
}
|
||||
|
||||
bool isServicesMatchingFilters(ScanFilter scanFilter, BleDevice device) {
|
||||
var serviceFilters = scanFilter.withServices;
|
||||
if (serviceFilters.isEmpty) return true;
|
||||
|
||||
List<String> serviceUuids = device.services;
|
||||
if (serviceUuids.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return serviceFilters.any(serviceUuids.contains);
|
||||
}
|
||||
|
||||
bool isManufacturerDataMatchingFilters(
|
||||
ScanFilter scanFilter,
|
||||
BleDevice device,
|
||||
) {
|
||||
final filterMfdList = scanFilter.withManufacturerData;
|
||||
if (filterMfdList.isEmpty) return true;
|
||||
|
||||
List<ManufacturerData> manufacturerDataList = device.manufacturerDataList;
|
||||
if (manufacturerDataList.isEmpty) return false;
|
||||
|
||||
return manufacturerDataList.any((deviceMfd) => filterMfdList.any(
|
||||
(filterMfd) => _isManufacturerDataMatch(filterMfd, deviceMfd),
|
||||
));
|
||||
}
|
||||
|
||||
bool _isManufacturerDataMatch(
|
||||
ManufacturerDataFilter filterMfd,
|
||||
ManufacturerData deviceMfd,
|
||||
) {
|
||||
if (filterMfd.companyIdentifier != deviceMfd.companyId) return false;
|
||||
|
||||
Uint8List? filterPayload = filterMfd.payload;
|
||||
Uint8List devicePayload = deviceMfd.payload;
|
||||
|
||||
if (filterPayload == null || filterPayload.isEmpty) return true;
|
||||
if (devicePayload.isEmpty) return false;
|
||||
if (filterPayload.length > devicePayload.length) return false;
|
||||
|
||||
Uint8List? filterMask = filterMfd.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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < filterPayload.length; i++) {
|
||||
if (filterPayload[i] != devicePayload[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bluez/bluez.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:universal_ble/src/models/model_exports.dart';
|
||||
import 'package:universal_ble/src/universal_ble_filter_util.dart';
|
||||
import 'package:universal_ble/src/universal_ble_platform_interface.dart';
|
||||
|
||||
class UniversalBleLinux extends UniversalBlePlatform {
|
||||
@@ -14,12 +14,13 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
bool isInitialized = false;
|
||||
|
||||
final BlueZClient _client = BlueZClient();
|
||||
|
||||
late final UniversalBleFilterUtil _bleFilter = UniversalBleFilterUtil();
|
||||
BlueZAdapter? _activeAdapter;
|
||||
ScanFilter? _scanFilter;
|
||||
Completer<void>? _initializationCompleter;
|
||||
final Map<String, BlueZDevice> _devices = {};
|
||||
final Map<String, StreamSubscription> _deviceStreamSubscriptions = {};
|
||||
final Map<String, StreamSubscription> _deviceUpdateStreamSubscriptions = {};
|
||||
final Map<String, StreamSubscription> _deviceAdvertisementSubscriptions = {};
|
||||
|
||||
final Map<String, StreamSubscription> _characteristicPropertiesSubscriptions =
|
||||
{};
|
||||
|
||||
@@ -58,15 +59,44 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
PlatformConfig? platformConfig,
|
||||
}) async {
|
||||
await _ensureInitialized();
|
||||
await super.startScan(scanFilter: scanFilter);
|
||||
if (_activeAdapter?.discovering != true) {
|
||||
// Add services filter
|
||||
_activeAdapter?.setDiscoveryFilter(
|
||||
uuids: scanFilter?.withServices.toValidUUIDList(),
|
||||
);
|
||||
_scanFilter = scanFilter;
|
||||
await _activeAdapter?.startDiscovery();
|
||||
_client.devices.forEach(_onDeviceAdd);
|
||||
var adapter = _activeAdapter;
|
||||
if (adapter == null) {
|
||||
throw "Adapter not available";
|
||||
}
|
||||
|
||||
// Stop scan and clean all old advertisement listeners
|
||||
await stopScan();
|
||||
|
||||
bool hasCustomFilter = scanFilter?.hasCustomFilter() ?? false;
|
||||
List<String> withServicesFilter = [];
|
||||
|
||||
if (hasCustomFilter) {
|
||||
_bleFilter.scanFilter = scanFilter;
|
||||
} else {
|
||||
_bleFilter.scanFilter = null;
|
||||
withServicesFilter = scanFilter?.withServices.toValidUUIDList() ?? [];
|
||||
}
|
||||
|
||||
// Add services filter
|
||||
await adapter.setDiscoveryFilter(
|
||||
uuids: withServicesFilter,
|
||||
);
|
||||
|
||||
await _activeAdapter?.startDiscovery();
|
||||
|
||||
// Apply custom Services filter to these devices
|
||||
ScanFilter customServicesFilter = ScanFilter(
|
||||
withServices: withServicesFilter,
|
||||
);
|
||||
for (var device in _client.devices) {
|
||||
if (!hasCustomFilter && withServicesFilter.isNotEmpty) {
|
||||
if (_bleFilter.isServicesMatchingFilters(
|
||||
customServicesFilter, device.toBleDevice())) {
|
||||
_onDeviceAdd(device);
|
||||
}
|
||||
} else {
|
||||
_onDeviceAdd(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +107,11 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
if (_activeAdapter?.discovering == true) {
|
||||
await _activeAdapter?.stopDiscovery();
|
||||
}
|
||||
// Clean all advertiseemnt listeners
|
||||
_deviceAdvertisementSubscriptions.removeWhere((e, value) {
|
||||
value.cancel();
|
||||
return true;
|
||||
});
|
||||
} catch (e) {
|
||||
UniversalBlePlatform.logInfo(
|
||||
"stopScan error: $e",
|
||||
@@ -139,6 +174,9 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
});
|
||||
}
|
||||
|
||||
// Few ble devices requires delay to perform operations after discovering services
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
if (device.gattServices.isEmpty && !device.servicesResolved) {
|
||||
throw "Failed to resolve services";
|
||||
}
|
||||
@@ -413,40 +451,41 @@ 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) {}
|
||||
BleDevice bleDevice = device.toBleDevice();
|
||||
if (!_bleFilter.filterDevice(bleDevice)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update scan results only if rssi is available
|
||||
if (device.rssi != 0) updateScanResult(device.toBleDevice());
|
||||
if (device.rssi != 0) {
|
||||
updateScanResult(bleDevice);
|
||||
}
|
||||
|
||||
// Setup Cache
|
||||
_devices[device.address] = device;
|
||||
|
||||
// Setup update listener
|
||||
if (_deviceStreamSubscriptions[device.address] != null) {
|
||||
_deviceStreamSubscriptions[device.address]?.cancel();
|
||||
}
|
||||
// Setup advertisements Listener
|
||||
_deviceAdvertisementSubscriptions[device.address] ??= device
|
||||
.propertiesChanged
|
||||
.where((e) =>
|
||||
e.contains(BluezProperty.rssi) ||
|
||||
e.contains(BluezProperty.manufacturerData) ||
|
||||
e.contains(BluezProperty.uuids))
|
||||
.listen((_) {
|
||||
if (_bleFilter.filterDevice(bleDevice)) {
|
||||
updateScanResult(device.toBleDevice());
|
||||
}
|
||||
});
|
||||
|
||||
_deviceStreamSubscriptions[device.address] =
|
||||
// Setup update listener
|
||||
_deviceUpdateStreamSubscriptions[device.address] ??=
|
||||
device.propertiesChanged.listen((properties) {
|
||||
for (final property in properties) {
|
||||
switch (property) {
|
||||
case BluezProperty.rssi:
|
||||
updateScanResult(device.toBleDevice());
|
||||
break;
|
||||
// Connection/Pair updates
|
||||
case BluezProperty.connected:
|
||||
updateConnection(device.address, device.connected);
|
||||
break;
|
||||
case BluezProperty.manufacturerData:
|
||||
updateScanResult(device.toBleDevice());
|
||||
break;
|
||||
case BluezProperty.paired:
|
||||
updatePairingState(device.address, device.paired);
|
||||
break;
|
||||
@@ -458,95 +497,37 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
case BluezProperty.txPower:
|
||||
case BluezProperty.address:
|
||||
case BluezProperty.addressType:
|
||||
case BluezProperty.rssi:
|
||||
case BluezProperty.manufacturerData:
|
||||
break;
|
||||
default:
|
||||
UniversalBlePlatform.logInfo(
|
||||
"UnhandledDevicePropertyChanged ${device.name} ${device.address}: $property");
|
||||
"UnhandledDevicePropertyChanged ${device.name} ${device.address}: $property",
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 (final filterMfd in filterMfdList) {
|
||||
// Check if device have manufacturerData for this filter
|
||||
for (final 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
|
||||
_deviceStreamSubscriptions[device.address]?.cancel();
|
||||
_deviceStreamSubscriptions
|
||||
.removeWhere((key, value) => key == device.address);
|
||||
// Clean Update listeners
|
||||
_deviceUpdateStreamSubscriptions.removeWhere((key, value) {
|
||||
if (key == device.address) {
|
||||
value.cancel();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// Clean Advertisement listeners
|
||||
_deviceAdvertisementSubscriptions.removeWhere((key, value) {
|
||||
if (key == device.address) {
|
||||
value.cancel();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,62 +551,6 @@ class BluezProperty {
|
||||
static const String propertyClass = 'Class';
|
||||
}
|
||||
|
||||
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);
|
||||
final sorted = manufacturerData.entries.toList()
|
||||
..sort((a, b) => a.key.id - b.key.id);
|
||||
int companyId = sorted.first.key.id;
|
||||
List<int> manufacturerDataValue = sorted.first.value;
|
||||
final byteData = ByteData(2);
|
||||
// TODO: Verify that this works regardless of the endianess
|
||||
byteData.setInt16(0, companyId, Endian.host);
|
||||
List<int> bytes = byteData.buffer.asUint8List();
|
||||
return Uint8List.fromList(bytes + manufacturerDataValue);
|
||||
} catch (e) {
|
||||
UniversalBlePlatform.logInfo(
|
||||
'Error parsing manufacturerData: $e',
|
||||
isError: true,
|
||||
);
|
||||
return Uint8List(0);
|
||||
}
|
||||
}
|
||||
|
||||
BleDevice toBleDevice({
|
||||
bool? isSystemDevice,
|
||||
}) {
|
||||
return BleDevice(
|
||||
name: alias,
|
||||
deviceId: address,
|
||||
isPaired: paired,
|
||||
manufacturerData: manufacturerDataHead,
|
||||
manufacturerDataHead: manufacturerDataHead,
|
||||
rssi: rssi,
|
||||
isSystemDevice: isSystemDevice,
|
||||
services: uuids.map((e) => e.toString()).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension on BlueZGattCharacteristicFlag {
|
||||
CharacteristicProperty? toCharacteristicProperty() {
|
||||
return switch (this) {
|
||||
@@ -664,3 +589,28 @@ extension on BlueZFailedException {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension on ScanFilter {
|
||||
bool hasCustomFilter() {
|
||||
return withNamePrefix.isNotEmpty || withManufacturerData.isNotEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
extension BlueZDeviceExtension on BlueZDevice {
|
||||
List<ManufacturerData> get manufacturerDataList => manufacturerData.entries
|
||||
.map((MapEntry<BlueZManufacturerId, List<int>> data) =>
|
||||
ManufacturerData(data.key.id, Uint8List.fromList(data.value)))
|
||||
.toList();
|
||||
|
||||
BleDevice toBleDevice({bool? isSystemDevice}) {
|
||||
return BleDevice(
|
||||
name: name,
|
||||
deviceId: address,
|
||||
isPaired: paired,
|
||||
rssi: rssi,
|
||||
isSystemDevice: isSystemDevice,
|
||||
services: uuids.map((e) => e.toString()).toList(),
|
||||
manufacturerDataList: manufacturerDataList,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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?>[];
|
||||
}
|
||||
@@ -32,8 +31,7 @@ class UniversalBleScanResult {
|
||||
this.name,
|
||||
this.isPaired,
|
||||
this.rssi,
|
||||
this.manufacturerData,
|
||||
this.manufacturerDataHead,
|
||||
this.manufacturerDataList,
|
||||
this.services,
|
||||
});
|
||||
|
||||
@@ -45,9 +43,7 @@ class UniversalBleScanResult {
|
||||
|
||||
int? rssi;
|
||||
|
||||
Uint8List? manufacturerData;
|
||||
|
||||
Uint8List? manufacturerDataHead;
|
||||
List<UniversalManufacturerData?>? manufacturerDataList;
|
||||
|
||||
List<String?>? services;
|
||||
|
||||
@@ -57,8 +53,7 @@ class UniversalBleScanResult {
|
||||
name,
|
||||
isPaired,
|
||||
rssi,
|
||||
manufacturerData,
|
||||
manufacturerDataHead,
|
||||
manufacturerDataList,
|
||||
services,
|
||||
];
|
||||
}
|
||||
@@ -70,9 +65,8 @@ class UniversalBleScanResult {
|
||||
name: result[1] as String?,
|
||||
isPaired: result[2] as bool?,
|
||||
rssi: result[3] as int?,
|
||||
manufacturerData: result[4] as Uint8List?,
|
||||
manufacturerDataHead: result[5] as Uint8List?,
|
||||
services: (result[6] as List<Object?>?)?.cast<String?>(),
|
||||
manufacturerDataList: (result[4] as List<Object?>?)?.cast<UniversalManufacturerData?>(),
|
||||
services: (result[5] as List<Object?>?)?.cast<String?>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -98,8 +92,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?>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -134,16 +127,20 @@ class UniversalBleCharacteristic {
|
||||
class UniversalScanFilter {
|
||||
UniversalScanFilter({
|
||||
required this.withServices,
|
||||
required this.withNamePrefix,
|
||||
required this.withManufacturerData,
|
||||
});
|
||||
|
||||
List<String?> withServices;
|
||||
|
||||
List<String?> withNamePrefix;
|
||||
|
||||
List<UniversalManufacturerDataFilter?> withManufacturerData;
|
||||
|
||||
Object encode() {
|
||||
return <Object?>[
|
||||
withServices,
|
||||
withNamePrefix,
|
||||
withManufacturerData,
|
||||
];
|
||||
}
|
||||
@@ -152,20 +149,20 @@ class UniversalScanFilter {
|
||||
result as List<Object?>;
|
||||
return UniversalScanFilter(
|
||||
withServices: (result[0] as List<Object?>?)!.cast<String?>(),
|
||||
withManufacturerData: (result[1] as List<Object?>?)!
|
||||
.cast<UniversalManufacturerDataFilter?>(),
|
||||
withNamePrefix: (result[1] as List<Object?>?)!.cast<String?>(),
|
||||
withManufacturerData: (result[2] as List<Object?>?)!.cast<UniversalManufacturerDataFilter?>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UniversalManufacturerDataFilter {
|
||||
UniversalManufacturerDataFilter({
|
||||
this.companyIdentifier,
|
||||
required this.companyIdentifier,
|
||||
this.data,
|
||||
this.mask,
|
||||
});
|
||||
|
||||
int? companyIdentifier;
|
||||
int companyIdentifier;
|
||||
|
||||
Uint8List? data;
|
||||
|
||||
@@ -182,13 +179,40 @@ class UniversalManufacturerDataFilter {
|
||||
static UniversalManufacturerDataFilter decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return UniversalManufacturerDataFilter(
|
||||
companyIdentifier: result[0] as int?,
|
||||
companyIdentifier: result[0]! as int,
|
||||
data: result[1] as Uint8List?,
|
||||
mask: result[2] as Uint8List?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UniversalManufacturerData {
|
||||
UniversalManufacturerData({
|
||||
required this.companyIdentifier,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
int companyIdentifier;
|
||||
|
||||
Uint8List data;
|
||||
|
||||
Object encode() {
|
||||
return <Object?>[
|
||||
companyIdentifier,
|
||||
data,
|
||||
];
|
||||
}
|
||||
|
||||
static UniversalManufacturerData decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return UniversalManufacturerData(
|
||||
companyIdentifier: result[0]! as int,
|
||||
data: result[1]! as Uint8List,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class _PigeonCodec extends StandardMessageCodec {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
@@ -196,18 +220,21 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
if (value is UniversalBleScanResult) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is UniversalBleService) {
|
||||
} else if (value is UniversalBleService) {
|
||||
buffer.putUint8(130);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is UniversalBleCharacteristic) {
|
||||
} else if (value is UniversalBleCharacteristic) {
|
||||
buffer.putUint8(131);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is UniversalScanFilter) {
|
||||
} else if (value is UniversalScanFilter) {
|
||||
buffer.putUint8(132);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is UniversalManufacturerDataFilter) {
|
||||
} else if (value is UniversalManufacturerDataFilter) {
|
||||
buffer.putUint8(133);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is UniversalManufacturerData) {
|
||||
buffer.putUint8(134);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
@@ -216,16 +243,18 @@ class _PigeonCodec extends StandardMessageCodec {
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 129:
|
||||
case 129:
|
||||
return UniversalBleScanResult.decode(readValue(buffer)!);
|
||||
case 130:
|
||||
case 130:
|
||||
return UniversalBleService.decode(readValue(buffer)!);
|
||||
case 131:
|
||||
case 131:
|
||||
return UniversalBleCharacteristic.decode(readValue(buffer)!);
|
||||
case 132:
|
||||
case 132:
|
||||
return UniversalScanFilter.decode(readValue(buffer)!);
|
||||
case 133:
|
||||
case 133:
|
||||
return UniversalManufacturerDataFilter.decode(readValue(buffer)!);
|
||||
case 134:
|
||||
return UniversalManufacturerData.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
@@ -237,11 +266,9 @@ class UniversalBlePlatformChannel {
|
||||
/// Constructor for [UniversalBlePlatformChannel]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
UniversalBlePlatformChannel(
|
||||
{BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||
UniversalBlePlatformChannel({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||
: __pigeon_binaryMessenger = binaryMessenger,
|
||||
__pigeon_messageChannelSuffix =
|
||||
messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
__pigeon_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
final BinaryMessenger? __pigeon_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
@@ -249,10 +276,8 @@ class UniversalBlePlatformChannel {
|
||||
final String __pigeon_messageChannelSuffix;
|
||||
|
||||
Future<int> getBluetoothAvailabilityState() async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getBluetoothAvailabilityState$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -278,10 +303,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<bool> enableBluetooth() async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.enableBluetooth$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -307,10 +330,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> startScan(UniversalScanFilter? filter) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.startScan$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -331,10 +352,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> stopScan() async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.stopScan$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -355,10 +374,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> connect(String deviceId) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -379,10 +396,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> disconnect(String deviceId) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.disconnect$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -402,19 +417,15 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setNotifiable(String deviceId, String service,
|
||||
String characteristic, int bleInputProperty) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
Future<void> setNotifiable(String deviceId, String service, String characteristic, int bleInputProperty) async {
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.setNotifiable$__pigeon_messageChannelSuffix';
|
||||
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) {
|
||||
@@ -429,10 +440,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<List<UniversalBleService?>> discoverServices(String deviceId) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.discoverServices$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -453,23 +462,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 {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
Future<Uint8List> readValue(String deviceId, String service, String characteristic) async {
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.readValue$__pigeon_messageChannelSuffix';
|
||||
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) {
|
||||
@@ -489,16 +494,14 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<int> requestMtu(String deviceId, int expectedMtu) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.requestMtu$__pigeon_messageChannelSuffix';
|
||||
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) {
|
||||
@@ -517,24 +520,15 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> writeValue(String deviceId, String service,
|
||||
String characteristic, Uint8List value, int bleOutputProperty) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
Future<void> writeValue(String deviceId, String service, String characteristic, Uint8List value, int bleOutputProperty) async {
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.writeValue$__pigeon_messageChannelSuffix';
|
||||
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) {
|
||||
@@ -549,10 +543,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<bool> isPaired(String deviceId) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isPaired$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -578,10 +570,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<bool> pair(String deviceId) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.pair$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -607,10 +597,8 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
|
||||
Future<void> unPair(String deviceId) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.unPair$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -630,12 +618,9 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<UniversalBleScanResult?>> getSystemDevices(
|
||||
List<String?> withServices) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
Future<List<UniversalBleScanResult?>> getSystemDevices(List<String?> withServices) async {
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getSystemDevices$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -656,16 +641,13 @@ 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?>();
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getConnectionState(String deviceId) async {
|
||||
final String __pigeon_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectionState$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
final String __pigeon_channelName = 'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.getConnectionState$__pigeon_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
__pigeon_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: __pigeon_binaryMessenger,
|
||||
@@ -701,30 +683,22 @@ 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, bool connected);
|
||||
|
||||
static void setUp(
|
||||
UniversalBleCallbackChannel? api, {
|
||||
BinaryMessenger? binaryMessenger,
|
||||
String messageChannelSuffix = '',
|
||||
}) {
|
||||
messageChannelSuffix =
|
||||
messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
static void setUp(UniversalBleCallbackChannel? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) {
|
||||
messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
{
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<
|
||||
Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onAvailabilityChanged$messageChannelSuffix', 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,
|
||||
@@ -734,25 +708,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$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onPairStateChange$messageChannelSuffix', 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,
|
||||
@@ -766,28 +737,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$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onScanResult$messageChannelSuffix', 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 {
|
||||
@@ -795,25 +762,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$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onValueChanged$messageChannelSuffix', 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,
|
||||
@@ -825,30 +789,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$messageChannelSuffix',
|
||||
pigeonChannelCodec,
|
||||
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBleCallbackChannel.onConnectionChanged$messageChannelSuffix', 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,
|
||||
@@ -861,9 +821,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()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
|
||||
ScanFilter? scanFilter,
|
||||
PlatformConfig? platformConfig,
|
||||
}) async {
|
||||
await super.startScan(scanFilter: scanFilter);
|
||||
await _channel.startScan(
|
||||
scanFilter.toUniversalScanFilter(),
|
||||
);
|
||||
@@ -194,22 +193,16 @@ class _UniversalBleCallbackHandler extends UniversalBleCallbackChannel {
|
||||
}
|
||||
|
||||
extension _UniversalBleScanResultExtension on UniversalBleScanResult {
|
||||
BleDevice toBleDevice({
|
||||
bool? isSystemDevice,
|
||||
}) {
|
||||
var mnfDataHead = manufacturerDataHead ?? Uint8List.fromList([]);
|
||||
var mnfData = manufacturerData ?? mnfDataHead;
|
||||
BleDevice toBleDevice({bool? isSystemDevice}) {
|
||||
return BleDevice(
|
||||
name: name,
|
||||
deviceId: deviceId,
|
||||
manufacturerData: mnfData,
|
||||
manufacturerDataHead: mnfDataHead,
|
||||
rssi: rssi,
|
||||
isPaired: isPaired,
|
||||
isSystemDevice: isSystemDevice,
|
||||
services: services
|
||||
?.where((e) => e != null)
|
||||
.map((e) => BleUuidParser.string(e!))
|
||||
services: services?.nonNulls.map(BleUuidParser.string).toList() ?? [],
|
||||
manufacturerDataList: manufacturerDataList?.nonNulls
|
||||
.map((e) => ManufacturerData(e.companyIdentifier, e.data))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
@@ -222,7 +215,7 @@ extension _ScanFilterExtension on ScanFilter? {
|
||||
?.withManufacturerData
|
||||
.map((e) => UniversalManufacturerDataFilter(
|
||||
companyIdentifier: e.companyIdentifier,
|
||||
data: e.data,
|
||||
data: e.payload,
|
||||
mask: e.mask,
|
||||
))
|
||||
.toList();
|
||||
@@ -230,6 +223,7 @@ extension _ScanFilterExtension on ScanFilter? {
|
||||
// Windows crashes if it's null, so we need to pass empty scan filter in this case
|
||||
return UniversalScanFilter(
|
||||
withServices: this?.withServices.toValidUUIDList() ?? [],
|
||||
withNamePrefix: this?.withNamePrefix ?? [],
|
||||
withManufacturerData: manufacturerDataFilters ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'dart:typed_data';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
abstract class UniversalBlePlatform {
|
||||
ScanFilter? _scanFilter;
|
||||
StreamController? _connectionStreamController;
|
||||
final Map<String, bool> _pairStateMap = {};
|
||||
|
||||
@@ -15,9 +14,7 @@ abstract class UniversalBlePlatform {
|
||||
Future<void> startScan({
|
||||
ScanFilter? scanFilter,
|
||||
PlatformConfig? platformConfig,
|
||||
}) async {
|
||||
_scanFilter = scanFilter;
|
||||
}
|
||||
});
|
||||
|
||||
Future<void> stopScan();
|
||||
|
||||
@@ -68,13 +65,6 @@ abstract class UniversalBlePlatform {
|
||||
}
|
||||
|
||||
void updateScanResult(BleDevice bleDevice) {
|
||||
// Filter by name
|
||||
ScanFilter? scanFilter = _scanFilter;
|
||||
if (scanFilter != null && scanFilter.withNamePrefix.isNotEmpty) {
|
||||
if (bleDevice.name == null ||
|
||||
!scanFilter.withNamePrefix
|
||||
.any((e) => bleDevice.name?.startsWith(e) == true)) return;
|
||||
}
|
||||
onScanResult?.call(bleDevice);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_web_bluetooth/flutter_web_bluetooth.dart';
|
||||
import 'package:universal_ble/src/models/model_exports.dart';
|
||||
import 'package:universal_ble/src/universal_ble_platform_interface.dart';
|
||||
@@ -95,8 +95,14 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
}
|
||||
|
||||
@override
|
||||
bool receivesAdvertisements(String deviceId) =>
|
||||
_getDeviceById(deviceId)?.hasWatchAdvertisements() ?? false;
|
||||
bool receivesAdvertisements(String deviceId) {
|
||||
// Advertisements do not work on Linux/Web even with the "Experimental Web Platform features" flag enabled. Verified with Chrome Version 128.0.6613.138
|
||||
if (kIsWeb && defaultTargetPlatform == TargetPlatform.linux) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _getDeviceById(deviceId)?.hasWatchAdvertisements() ?? false;
|
||||
}
|
||||
|
||||
/// This will work only if `chrome://flags/#enable-experimental-web-platform-features` is enabled
|
||||
Future<void> _watchDeviceAdvertisements(BluetoothDevice device) async {
|
||||
@@ -342,22 +348,22 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
|
||||
RequestOptionsBuilder _getRequestOptionBuilder(
|
||||
ScanFilter? scanFilter,
|
||||
WebConfig? webConfig,
|
||||
WebOptions? webOptions,
|
||||
) {
|
||||
List<RequestFilterBuilder> filters = [];
|
||||
List<int> optionalManufacturerData = [];
|
||||
List<String> optionalServices = [];
|
||||
|
||||
if (webConfig != null) {
|
||||
optionalServices.addAll(webConfig.optionalServices.toValidUUIDList());
|
||||
optionalManufacturerData.addAll(webConfig.optionalManufacturerData);
|
||||
if (webOptions != null) {
|
||||
optionalServices.addAll(webOptions.optionalServices.toValidUUIDList());
|
||||
optionalManufacturerData.addAll(webOptions.optionalManufacturerData);
|
||||
}
|
||||
|
||||
if (scanFilter != null) {
|
||||
// Add services filter
|
||||
for (var service in scanFilter.withServices.toValidUUIDList()) {
|
||||
filters.add(RequestFilterBuilder(services: [service]));
|
||||
if (webConfig == null || webConfig.optionalServices.isEmpty) {
|
||||
if (webOptions == null || webOptions.optionalServices.isEmpty) {
|
||||
optionalServices.add(service);
|
||||
}
|
||||
}
|
||||
@@ -369,19 +375,16 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
manufacturerData: [
|
||||
ManufacturerDataFilterBuilder(
|
||||
companyIdentifier: manufacturerData.companyIdentifier,
|
||||
dataPrefix: manufacturerData.data,
|
||||
dataPrefix: manufacturerData.payload,
|
||||
mask: manufacturerData.mask,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// Add optionalManufacturerData from scanFilter if webConfig is not provided
|
||||
if (webConfig == null || webConfig.optionalManufacturerData.isEmpty) {
|
||||
int? companyId = manufacturerData.companyIdentifier;
|
||||
if (companyId != null) {
|
||||
optionalManufacturerData.add(companyId);
|
||||
}
|
||||
// Add optionalManufacturerData from scanFilter if webOptions is not provided
|
||||
if (webOptions == null || webOptions.optionalManufacturerData.isEmpty) {
|
||||
optionalManufacturerData.add(manufacturerData.companyIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,8 +425,7 @@ extension _BluetoothDeviceExtension on BluetoothDevice {
|
||||
return BleDevice(
|
||||
name: name,
|
||||
deviceId: id,
|
||||
manufacturerData: manufacturerDataMap?.toUint8List(),
|
||||
manufacturerDataHead: manufacturerDataMap?.toUint8List(),
|
||||
manufacturerDataList: manufacturerDataMap?.toManufacturerDataList() ?? [],
|
||||
rssi: rssi,
|
||||
services: services,
|
||||
);
|
||||
@@ -431,17 +433,10 @@ extension _BluetoothDeviceExtension on BluetoothDevice {
|
||||
}
|
||||
|
||||
extension _UnmodifiableMapViewExtension on UnmodifiableMapView<int, ByteData> {
|
||||
Uint8List? toUint8List() {
|
||||
List<MapEntry<int, ByteData>> sorted = entries.toList()
|
||||
..sort((a, b) => a.key - b.key);
|
||||
if (sorted.isEmpty) return null;
|
||||
int companyId = sorted.first.key;
|
||||
List<int> manufacturerDataValue = sorted.first.value.buffer.asUint8List();
|
||||
final byteData = ByteData(2);
|
||||
byteData.setInt16(0, companyId, Endian.host);
|
||||
List<int> bytes = byteData.buffer.asUint8List();
|
||||
return Uint8List.fromList(bytes + manufacturerDataValue);
|
||||
}
|
||||
List<ManufacturerData>? toManufacturerDataList() => entries
|
||||
.map((MapEntry<int, ByteData> data) =>
|
||||
ManufacturerData(data.key, data.value.buffer.asUint8List()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
class _UniversalWebBluetoothService {
|
||||
|
||||
@@ -107,8 +107,7 @@ class UniversalBleScanResult {
|
||||
final String? name;
|
||||
final bool? isPaired;
|
||||
final int? rssi;
|
||||
final Uint8List? manufacturerData;
|
||||
final Uint8List? manufacturerDataHead;
|
||||
final List<UniversalManufacturerData?>? manufacturerDataList;
|
||||
final List<String?>? services;
|
||||
|
||||
UniversalBleScanResult({
|
||||
@@ -116,8 +115,7 @@ class UniversalBleScanResult {
|
||||
required this.deviceId,
|
||||
required this.isPaired,
|
||||
required this.rssi,
|
||||
required this.manufacturerData,
|
||||
required this.manufacturerDataHead,
|
||||
required this.manufacturerDataList,
|
||||
required this.services,
|
||||
});
|
||||
}
|
||||
@@ -137,21 +135,33 @@ class UniversalBleCharacteristic {
|
||||
/// Scan Filters
|
||||
class UniversalScanFilter {
|
||||
final List<String?> withServices;
|
||||
final List<String?> withNamePrefix;
|
||||
final List<UniversalManufacturerDataFilter?> withManufacturerData;
|
||||
|
||||
UniversalScanFilter(
|
||||
this.withServices,
|
||||
this.withNamePrefix,
|
||||
this.withManufacturerData,
|
||||
);
|
||||
}
|
||||
|
||||
class UniversalManufacturerDataFilter {
|
||||
int? companyIdentifier;
|
||||
int companyIdentifier;
|
||||
Uint8List? data;
|
||||
Uint8List? mask;
|
||||
UniversalManufacturerDataFilter({
|
||||
this.companyIdentifier,
|
||||
required this.companyIdentifier,
|
||||
this.data,
|
||||
this.mask,
|
||||
});
|
||||
}
|
||||
|
||||
class UniversalManufacturerData {
|
||||
final int companyIdentifier;
|
||||
final Uint8List data;
|
||||
|
||||
UniversalManufacturerData({
|
||||
required this.companyIdentifier,
|
||||
required this.data,
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name: universal_ble
|
||||
description: A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter
|
||||
version: 0.12.1
|
||||
version: 0.13.0
|
||||
homepage: https://navideck.com
|
||||
repository: https://github.com/Navideck/universal_ble
|
||||
issue_tracker: https://github.com/Navideck/universal_ble/issues
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:universal_ble/src/universal_ble_filter_util.dart';
|
||||
import 'package:universal_ble/universal_ble.dart';
|
||||
|
||||
void main() {
|
||||
final universalBleFilter = UniversalBleFilterUtil();
|
||||
var device1 = BleDevice(
|
||||
deviceId: '1',
|
||||
name: '1_device',
|
||||
services: ['1_ser'],
|
||||
manufacturerDataList: [
|
||||
ManufacturerData(0x01, Uint8List.fromList([1, 2, 3])),
|
||||
],
|
||||
);
|
||||
var device2 = BleDevice(
|
||||
deviceId: '2',
|
||||
name: '2_device',
|
||||
services: ['2_ser'],
|
||||
manufacturerDataList: [
|
||||
ManufacturerData(0x02, Uint8List.fromList([1, 2, 3]))
|
||||
],
|
||||
);
|
||||
var device3 = BleDevice(
|
||||
deviceId: '3',
|
||||
name: '3_device',
|
||||
services: ['3_ser'],
|
||||
manufacturerDataList: [
|
||||
ManufacturerData(0x03, Uint8List.fromList([1, 2, 3]))
|
||||
],
|
||||
);
|
||||
|
||||
group("Test Individual Filter", () {
|
||||
test('Test isNameMatchingFilters', () {
|
||||
var scanFilter = ScanFilter(
|
||||
withNamePrefix: ['1', '2'],
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.isNameMatchingFilters(scanFilter, device1),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.isNameMatchingFilters(scanFilter, device2),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.isNameMatchingFilters(scanFilter, device3),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
test('Test isServicesMatchingFilters', () {
|
||||
var scanFilter = ScanFilter(
|
||||
withServices: ['1_ser', 'random', '3_ser'],
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.isServicesMatchingFilters(scanFilter, device1),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.isServicesMatchingFilters(scanFilter, device2),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.isNameMatchingFilters(scanFilter, device3),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('Test isManufacturerDataMatchingFilters', () {
|
||||
var scanFilter = ScanFilter(withManufacturerData: [
|
||||
ManufacturerDataFilter(
|
||||
companyIdentifier: 0x01,
|
||||
payload: Uint8List.fromList([1, 2]),
|
||||
),
|
||||
ManufacturerDataFilter(
|
||||
companyIdentifier: 0x02,
|
||||
),
|
||||
ManufacturerDataFilter(
|
||||
companyIdentifier: 0x03,
|
||||
payload: Uint8List.fromList([3, 4]),
|
||||
)
|
||||
]);
|
||||
expect(
|
||||
universalBleFilter.isManufacturerDataMatchingFilters(
|
||||
scanFilter,
|
||||
device1,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.isManufacturerDataMatchingFilters(
|
||||
scanFilter,
|
||||
device2,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.isManufacturerDataMatchingFilters(
|
||||
scanFilter,
|
||||
device3,
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group("Test Scan Filter", () {
|
||||
test('Test filterDevice: Have filter for all', () {
|
||||
universalBleFilter.scanFilter = ScanFilter(
|
||||
withNamePrefix: ['1'],
|
||||
withServices: ['3_ser'],
|
||||
withManufacturerData: [
|
||||
ManufacturerDataFilter(
|
||||
companyIdentifier: 0x02,
|
||||
)
|
||||
],
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device1),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device2),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device3),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
test('Test filterDevice: Filter for one', () {
|
||||
universalBleFilter.scanFilter = ScanFilter(
|
||||
withNamePrefix: ['1'],
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device1),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device2),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device3),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
test('Test filterDevice: Filter for two', () {
|
||||
universalBleFilter.scanFilter = ScanFilter(
|
||||
withNamePrefix: ['1', '2'],
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device1),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device2),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device3),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
test('Test filterDevice: Empty Filter', () {
|
||||
universalBleFilter.scanFilter = ScanFilter();
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device1),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device2),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device3),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
test('Test filterDevice: Null Filter', () {
|
||||
universalBleFilter.scanFilter = ScanFilter();
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device1),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device2),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
universalBleFilter.filterDevice(device3),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -42,6 +42,8 @@ list(APPEND PLUGIN_SOURCES
|
||||
"src/generated/universal_ble.g.cpp"
|
||||
"src/generated/universal_ble.g.h"
|
||||
"src/pin_entry.h"
|
||||
"src/universal_ble_filter_util.cpp"
|
||||
"src/universal_ble_filter_util.h"
|
||||
)
|
||||
|
||||
add_library(${PLUGIN_NAME} SHARED
|
||||
|
||||
@@ -38,15 +38,13 @@ UniversalBleScanResult::UniversalBleScanResult(
|
||||
const std::string* name,
|
||||
const bool* is_paired,
|
||||
const int64_t* rssi,
|
||||
const std::vector<uint8_t>* manufacturer_data,
|
||||
const std::vector<uint8_t>* manufacturer_data_head,
|
||||
const EncodableList* manufacturer_data_list,
|
||||
const EncodableList* services)
|
||||
: device_id_(device_id),
|
||||
name_(name ? std::optional<std::string>(*name) : std::nullopt),
|
||||
is_paired_(is_paired ? std::optional<bool>(*is_paired) : std::nullopt),
|
||||
rssi_(rssi ? std::optional<int64_t>(*rssi) : std::nullopt),
|
||||
manufacturer_data_(manufacturer_data ? std::optional<std::vector<uint8_t>>(*manufacturer_data) : std::nullopt),
|
||||
manufacturer_data_head_(manufacturer_data_head ? std::optional<std::vector<uint8_t>>(*manufacturer_data_head) : std::nullopt),
|
||||
manufacturer_data_list_(manufacturer_data_list ? std::optional<EncodableList>(*manufacturer_data_list) : std::nullopt),
|
||||
services_(services ? std::optional<EncodableList>(*services) : std::nullopt) {}
|
||||
|
||||
const std::string& UniversalBleScanResult::device_id() const {
|
||||
@@ -97,29 +95,16 @@ void UniversalBleScanResult::set_rssi(int64_t value_arg) {
|
||||
}
|
||||
|
||||
|
||||
const std::vector<uint8_t>* UniversalBleScanResult::manufacturer_data() const {
|
||||
return manufacturer_data_ ? &(*manufacturer_data_) : nullptr;
|
||||
const EncodableList* UniversalBleScanResult::manufacturer_data_list() const {
|
||||
return manufacturer_data_list_ ? &(*manufacturer_data_list_) : nullptr;
|
||||
}
|
||||
|
||||
void UniversalBleScanResult::set_manufacturer_data(const std::vector<uint8_t>* value_arg) {
|
||||
manufacturer_data_ = value_arg ? std::optional<std::vector<uint8_t>>(*value_arg) : std::nullopt;
|
||||
void UniversalBleScanResult::set_manufacturer_data_list(const EncodableList* value_arg) {
|
||||
manufacturer_data_list_ = value_arg ? std::optional<EncodableList>(*value_arg) : std::nullopt;
|
||||
}
|
||||
|
||||
void UniversalBleScanResult::set_manufacturer_data(const std::vector<uint8_t>& value_arg) {
|
||||
manufacturer_data_ = value_arg;
|
||||
}
|
||||
|
||||
|
||||
const std::vector<uint8_t>* UniversalBleScanResult::manufacturer_data_head() const {
|
||||
return manufacturer_data_head_ ? &(*manufacturer_data_head_) : nullptr;
|
||||
}
|
||||
|
||||
void UniversalBleScanResult::set_manufacturer_data_head(const std::vector<uint8_t>* value_arg) {
|
||||
manufacturer_data_head_ = value_arg ? std::optional<std::vector<uint8_t>>(*value_arg) : std::nullopt;
|
||||
}
|
||||
|
||||
void UniversalBleScanResult::set_manufacturer_data_head(const std::vector<uint8_t>& value_arg) {
|
||||
manufacturer_data_head_ = value_arg;
|
||||
void UniversalBleScanResult::set_manufacturer_data_list(const EncodableList& value_arg) {
|
||||
manufacturer_data_list_ = value_arg;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,13 +123,12 @@ void UniversalBleScanResult::set_services(const EncodableList& value_arg) {
|
||||
|
||||
EncodableList UniversalBleScanResult::ToEncodableList() const {
|
||||
EncodableList list;
|
||||
list.reserve(7);
|
||||
list.reserve(6);
|
||||
list.push_back(EncodableValue(device_id_));
|
||||
list.push_back(name_ ? EncodableValue(*name_) : EncodableValue());
|
||||
list.push_back(is_paired_ ? EncodableValue(*is_paired_) : EncodableValue());
|
||||
list.push_back(rssi_ ? EncodableValue(*rssi_) : EncodableValue());
|
||||
list.push_back(manufacturer_data_ ? EncodableValue(*manufacturer_data_) : EncodableValue());
|
||||
list.push_back(manufacturer_data_head_ ? EncodableValue(*manufacturer_data_head_) : EncodableValue());
|
||||
list.push_back(manufacturer_data_list_ ? EncodableValue(*manufacturer_data_list_) : EncodableValue());
|
||||
list.push_back(services_ ? EncodableValue(*services_) : EncodableValue());
|
||||
return list;
|
||||
}
|
||||
@@ -164,15 +148,11 @@ UniversalBleScanResult UniversalBleScanResult::FromEncodableList(const Encodable
|
||||
if (!encodable_rssi.IsNull()) {
|
||||
decoded.set_rssi(encodable_rssi.LongValue());
|
||||
}
|
||||
auto& encodable_manufacturer_data = list[4];
|
||||
if (!encodable_manufacturer_data.IsNull()) {
|
||||
decoded.set_manufacturer_data(std::get<std::vector<uint8_t>>(encodable_manufacturer_data));
|
||||
auto& encodable_manufacturer_data_list = list[4];
|
||||
if (!encodable_manufacturer_data_list.IsNull()) {
|
||||
decoded.set_manufacturer_data_list(std::get<EncodableList>(encodable_manufacturer_data_list));
|
||||
}
|
||||
auto& encodable_manufacturer_data_head = list[5];
|
||||
if (!encodable_manufacturer_data_head.IsNull()) {
|
||||
decoded.set_manufacturer_data_head(std::get<std::vector<uint8_t>>(encodable_manufacturer_data_head));
|
||||
}
|
||||
auto& encodable_services = list[6];
|
||||
auto& encodable_services = list[5];
|
||||
if (!encodable_services.IsNull()) {
|
||||
decoded.set_services(std::get<EncodableList>(encodable_services));
|
||||
}
|
||||
@@ -275,8 +255,10 @@ UniversalBleCharacteristic UniversalBleCharacteristic::FromEncodableList(const E
|
||||
|
||||
UniversalScanFilter::UniversalScanFilter(
|
||||
const EncodableList& with_services,
|
||||
const EncodableList& with_name_prefix,
|
||||
const EncodableList& with_manufacturer_data)
|
||||
: with_services_(with_services),
|
||||
with_name_prefix_(with_name_prefix),
|
||||
with_manufacturer_data_(with_manufacturer_data) {}
|
||||
|
||||
const EncodableList& UniversalScanFilter::with_services() const {
|
||||
@@ -288,6 +270,15 @@ void UniversalScanFilter::set_with_services(const EncodableList& value_arg) {
|
||||
}
|
||||
|
||||
|
||||
const EncodableList& UniversalScanFilter::with_name_prefix() const {
|
||||
return with_name_prefix_;
|
||||
}
|
||||
|
||||
void UniversalScanFilter::set_with_name_prefix(const EncodableList& value_arg) {
|
||||
with_name_prefix_ = value_arg;
|
||||
}
|
||||
|
||||
|
||||
const EncodableList& UniversalScanFilter::with_manufacturer_data() const {
|
||||
return with_manufacturer_data_;
|
||||
}
|
||||
@@ -299,8 +290,9 @@ void UniversalScanFilter::set_with_manufacturer_data(const EncodableList& value_
|
||||
|
||||
EncodableList UniversalScanFilter::ToEncodableList() const {
|
||||
EncodableList list;
|
||||
list.reserve(2);
|
||||
list.reserve(3);
|
||||
list.push_back(EncodableValue(with_services_));
|
||||
list.push_back(EncodableValue(with_name_prefix_));
|
||||
list.push_back(EncodableValue(with_manufacturer_data_));
|
||||
return list;
|
||||
}
|
||||
@@ -308,28 +300,26 @@ EncodableList UniversalScanFilter::ToEncodableList() const {
|
||||
UniversalScanFilter UniversalScanFilter::FromEncodableList(const EncodableList& list) {
|
||||
UniversalScanFilter decoded(
|
||||
std::get<EncodableList>(list[0]),
|
||||
std::get<EncodableList>(list[1]));
|
||||
std::get<EncodableList>(list[1]),
|
||||
std::get<EncodableList>(list[2]));
|
||||
return decoded;
|
||||
}
|
||||
|
||||
// UniversalManufacturerDataFilter
|
||||
|
||||
UniversalManufacturerDataFilter::UniversalManufacturerDataFilter() {}
|
||||
UniversalManufacturerDataFilter::UniversalManufacturerDataFilter(int64_t company_identifier)
|
||||
: company_identifier_(company_identifier) {}
|
||||
|
||||
UniversalManufacturerDataFilter::UniversalManufacturerDataFilter(
|
||||
const int64_t* company_identifier,
|
||||
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),
|
||||
: company_identifier_(company_identifier),
|
||||
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;
|
||||
int64_t UniversalManufacturerDataFilter::company_identifier() const {
|
||||
return company_identifier_;
|
||||
}
|
||||
|
||||
void UniversalManufacturerDataFilter::set_company_identifier(int64_t value_arg) {
|
||||
@@ -366,18 +356,15 @@ void UniversalManufacturerDataFilter::set_mask(const std::vector<uint8_t>& value
|
||||
EncodableList UniversalManufacturerDataFilter::ToEncodableList() const {
|
||||
EncodableList list;
|
||||
list.reserve(3);
|
||||
list.push_back(company_identifier_ ? EncodableValue(*company_identifier_) : EncodableValue());
|
||||
list.push_back(EncodableValue(company_identifier_));
|
||||
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());
|
||||
}
|
||||
UniversalManufacturerDataFilter decoded(
|
||||
list[0].LongValue());
|
||||
auto& encodable_data = list[1];
|
||||
if (!encodable_data.IsNull()) {
|
||||
decoded.set_data(std::get<std::vector<uint8_t>>(encodable_data));
|
||||
@@ -389,6 +376,47 @@ UniversalManufacturerDataFilter UniversalManufacturerDataFilter::FromEncodableLi
|
||||
return decoded;
|
||||
}
|
||||
|
||||
// UniversalManufacturerData
|
||||
|
||||
UniversalManufacturerData::UniversalManufacturerData(
|
||||
int64_t company_identifier,
|
||||
const std::vector<uint8_t>& data)
|
||||
: company_identifier_(company_identifier),
|
||||
data_(data) {}
|
||||
|
||||
int64_t UniversalManufacturerData::company_identifier() const {
|
||||
return company_identifier_;
|
||||
}
|
||||
|
||||
void UniversalManufacturerData::set_company_identifier(int64_t value_arg) {
|
||||
company_identifier_ = value_arg;
|
||||
}
|
||||
|
||||
|
||||
const std::vector<uint8_t>& UniversalManufacturerData::data() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
void UniversalManufacturerData::set_data(const std::vector<uint8_t>& value_arg) {
|
||||
data_ = value_arg;
|
||||
}
|
||||
|
||||
|
||||
EncodableList UniversalManufacturerData::ToEncodableList() const {
|
||||
EncodableList list;
|
||||
list.reserve(2);
|
||||
list.push_back(EncodableValue(company_identifier_));
|
||||
list.push_back(EncodableValue(data_));
|
||||
return list;
|
||||
}
|
||||
|
||||
UniversalManufacturerData UniversalManufacturerData::FromEncodableList(const EncodableList& list) {
|
||||
UniversalManufacturerData decoded(
|
||||
list[0].LongValue(),
|
||||
std::get<std::vector<uint8_t>>(list[1]));
|
||||
return decoded;
|
||||
}
|
||||
|
||||
|
||||
PigeonCodecSerializer::PigeonCodecSerializer() {}
|
||||
|
||||
@@ -406,6 +434,8 @@ EncodableValue PigeonCodecSerializer::ReadValueOfType(
|
||||
return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
|
||||
case 133:
|
||||
return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
|
||||
case 134:
|
||||
return CustomEncodableValue(UniversalManufacturerData::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
|
||||
default:
|
||||
return flutter::StandardCodecSerializer::ReadValueOfType(type, stream);
|
||||
}
|
||||
@@ -440,6 +470,11 @@ void PigeonCodecSerializer::WriteValue(
|
||||
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerDataFilter>(*custom_value).ToEncodableList()), stream);
|
||||
return;
|
||||
}
|
||||
if (custom_value->type() == typeid(UniversalManufacturerData)) {
|
||||
stream->WriteByte(134);
|
||||
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerData>(*custom_value).ToEncodableList()), stream);
|
||||
return;
|
||||
}
|
||||
}
|
||||
flutter::StandardCodecSerializer::WriteValue(value, stream);
|
||||
}
|
||||
|
||||
@@ -69,8 +69,7 @@ class UniversalBleScanResult {
|
||||
const std::string* name,
|
||||
const bool* is_paired,
|
||||
const int64_t* rssi,
|
||||
const std::vector<uint8_t>* manufacturer_data,
|
||||
const std::vector<uint8_t>* manufacturer_data_head,
|
||||
const flutter::EncodableList* manufacturer_data_list,
|
||||
const flutter::EncodableList* services);
|
||||
|
||||
const std::string& device_id() const;
|
||||
@@ -88,13 +87,9 @@ class UniversalBleScanResult {
|
||||
void set_rssi(const int64_t* value_arg);
|
||||
void set_rssi(int64_t value_arg);
|
||||
|
||||
const std::vector<uint8_t>* manufacturer_data() const;
|
||||
void set_manufacturer_data(const std::vector<uint8_t>* value_arg);
|
||||
void set_manufacturer_data(const std::vector<uint8_t>& value_arg);
|
||||
|
||||
const std::vector<uint8_t>* manufacturer_data_head() const;
|
||||
void set_manufacturer_data_head(const std::vector<uint8_t>* value_arg);
|
||||
void set_manufacturer_data_head(const std::vector<uint8_t>& value_arg);
|
||||
const flutter::EncodableList* manufacturer_data_list() const;
|
||||
void set_manufacturer_data_list(const flutter::EncodableList* value_arg);
|
||||
void set_manufacturer_data_list(const flutter::EncodableList& value_arg);
|
||||
|
||||
const flutter::EncodableList* services() const;
|
||||
void set_services(const flutter::EncodableList* value_arg);
|
||||
@@ -111,8 +106,7 @@ class UniversalBleScanResult {
|
||||
std::optional<std::string> name_;
|
||||
std::optional<bool> is_paired_;
|
||||
std::optional<int64_t> rssi_;
|
||||
std::optional<std::vector<uint8_t>> manufacturer_data_;
|
||||
std::optional<std::vector<uint8_t>> manufacturer_data_head_;
|
||||
std::optional<flutter::EncodableList> manufacturer_data_list_;
|
||||
std::optional<flutter::EncodableList> services_;
|
||||
|
||||
};
|
||||
@@ -184,11 +178,15 @@ class UniversalScanFilter {
|
||||
// Constructs an object setting all fields.
|
||||
explicit UniversalScanFilter(
|
||||
const flutter::EncodableList& with_services,
|
||||
const flutter::EncodableList& with_name_prefix,
|
||||
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_name_prefix() const;
|
||||
void set_with_name_prefix(const flutter::EncodableList& value_arg);
|
||||
|
||||
const flutter::EncodableList& with_manufacturer_data() const;
|
||||
void set_with_manufacturer_data(const flutter::EncodableList& value_arg);
|
||||
|
||||
@@ -200,6 +198,7 @@ class UniversalScanFilter {
|
||||
friend class UniversalBleCallbackChannel;
|
||||
friend class PigeonCodecSerializer;
|
||||
flutter::EncodableList with_services_;
|
||||
flutter::EncodableList with_name_prefix_;
|
||||
flutter::EncodableList with_manufacturer_data_;
|
||||
|
||||
};
|
||||
@@ -209,16 +208,15 @@ class UniversalScanFilter {
|
||||
class UniversalManufacturerDataFilter {
|
||||
public:
|
||||
// Constructs an object setting all non-nullable fields.
|
||||
UniversalManufacturerDataFilter();
|
||||
explicit UniversalManufacturerDataFilter(int64_t company_identifier);
|
||||
|
||||
// Constructs an object setting all fields.
|
||||
explicit UniversalManufacturerDataFilter(
|
||||
const int64_t* company_identifier,
|
||||
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);
|
||||
int64_t company_identifier() const;
|
||||
void set_company_identifier(int64_t value_arg);
|
||||
|
||||
const std::vector<uint8_t>* data() const;
|
||||
@@ -236,12 +234,39 @@ class UniversalManufacturerDataFilter {
|
||||
friend class UniversalBlePlatformChannel;
|
||||
friend class UniversalBleCallbackChannel;
|
||||
friend class PigeonCodecSerializer;
|
||||
std::optional<int64_t> company_identifier_;
|
||||
int64_t company_identifier_;
|
||||
std::optional<std::vector<uint8_t>> data_;
|
||||
std::optional<std::vector<uint8_t>> mask_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Generated class from Pigeon that represents data sent in messages.
|
||||
class UniversalManufacturerData {
|
||||
public:
|
||||
// Constructs an object setting all fields.
|
||||
explicit UniversalManufacturerData(
|
||||
int64_t company_identifier,
|
||||
const std::vector<uint8_t>& data);
|
||||
|
||||
int64_t company_identifier() const;
|
||||
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);
|
||||
|
||||
|
||||
private:
|
||||
static UniversalManufacturerData FromEncodableList(const flutter::EncodableList& list);
|
||||
flutter::EncodableList ToEncodableList() const;
|
||||
friend class UniversalBlePlatformChannel;
|
||||
friend class UniversalBleCallbackChannel;
|
||||
friend class PigeonCodecSerializer;
|
||||
int64_t company_identifier_;
|
||||
std::vector<uint8_t> data_;
|
||||
|
||||
};
|
||||
|
||||
class PigeonCodecSerializer : public flutter::StandardCodecSerializer {
|
||||
public:
|
||||
PigeonCodecSerializer();
|
||||
|
||||
@@ -158,4 +158,4 @@ namespace universal_ble
|
||||
return rove.dwMajorVersion == 10 && rove.dwBuildNumber >= 22000;
|
||||
}
|
||||
|
||||
} // namespace SimpleBLE
|
||||
} // namespace universal_ble
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <sdkddkver.h>
|
||||
#include <vector>
|
||||
#include "helper/utils.h"
|
||||
#include "generated/universal_ble.g.h"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace universal_ble
|
||||
{
|
||||
std::vector<UniversalManufacturerDataFilter> manufacturerScanFilter = std::vector<UniversalManufacturerDataFilter>();
|
||||
std::vector<winrt::guid> serviceFilterUUIDS = std::vector<winrt::guid>();
|
||||
std::vector<std::string> namePrefixFilter = std::vector<std::string>();
|
||||
|
||||
void setScanFilter(const UniversalScanFilter filter)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
// Set Services Filter
|
||||
if (!filter.with_services().empty())
|
||||
{
|
||||
for (const auto &uuid : filter.with_services())
|
||||
{
|
||||
serviceFilterUUIDS.push_back(uuid_to_guid(std::get<std::string>(uuid)));
|
||||
}
|
||||
}
|
||||
// Set Names Filter
|
||||
if (!filter.with_name_prefix().empty())
|
||||
{
|
||||
for (const auto &name : filter.with_name_prefix())
|
||||
{
|
||||
namePrefixFilter.push_back(std::get<std::string>(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resetScanFilter()
|
||||
{
|
||||
manufacturerScanFilter.clear();
|
||||
serviceFilterUUIDS.clear();
|
||||
namePrefixFilter.clear();
|
||||
}
|
||||
|
||||
bool isNameMatchingFilters(const std::string *name)
|
||||
{
|
||||
if (namePrefixFilter.empty())
|
||||
return true;
|
||||
if (name == nullptr || name->empty())
|
||||
return false;
|
||||
return std::any_of(namePrefixFilter.begin(), namePrefixFilter.end(),
|
||||
[&name](const flutter::EncodableValue &prefix)
|
||||
{
|
||||
return name->find(std::get<std::string>(prefix)) == 0;
|
||||
});
|
||||
}
|
||||
|
||||
bool isServicesMatchingFilters(const flutter::EncodableList *services)
|
||||
{
|
||||
if (serviceFilterUUIDS.empty())
|
||||
return true;
|
||||
if (services == nullptr || services->empty())
|
||||
return false;
|
||||
|
||||
std::unordered_set<winrt::guid> serviceGUIDs;
|
||||
serviceGUIDs.reserve(services->size());
|
||||
for (const auto &service : *services)
|
||||
{
|
||||
if (const auto *str = std::get_if<std::string>(&service))
|
||||
{
|
||||
serviceGUIDs.insert(uuid_to_guid(*str));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any of the filter UUIDs are in the set of service GUIDs
|
||||
return std::any_of(serviceFilterUUIDS.begin(), serviceFilterUUIDS.end(),
|
||||
[&serviceGUIDs](const winrt::guid &filterUUID)
|
||||
{
|
||||
return serviceGUIDs.find(filterUUID) != serviceGUIDs.end();
|
||||
});
|
||||
}
|
||||
|
||||
bool isManufacturerDataMatchingFilters(const flutter::EncodableList *manufacturerDataList)
|
||||
{
|
||||
if (manufacturerScanFilter.empty())
|
||||
return true;
|
||||
if (manufacturerDataList == nullptr || manufacturerDataList->empty())
|
||||
return false;
|
||||
|
||||
for (const auto &filter : manufacturerScanFilter)
|
||||
{
|
||||
const auto *data_filter = filter.data();
|
||||
const auto *mask = filter.mask();
|
||||
|
||||
for (const auto &value : *manufacturerDataList)
|
||||
{
|
||||
const auto &deviceMfData = std::any_cast<const UniversalManufacturerData &>(
|
||||
std::get<flutter::CustomEncodableValue>(value));
|
||||
|
||||
if (deviceMfData.company_identifier() != filter.company_identifier())
|
||||
continue;
|
||||
|
||||
// If no data filter, all data matches
|
||||
if (data_filter == nullptr)
|
||||
return true;
|
||||
|
||||
const auto &deviceData = deviceMfData.data();
|
||||
if (deviceData.size() < data_filter->size())
|
||||
continue;
|
||||
|
||||
bool isMatch = true;
|
||||
for (size_t i = 0; i < data_filter->size(); ++i)
|
||||
{
|
||||
const bool hasMask = mask != nullptr && i < mask->size();
|
||||
const uint8_t maskByte = hasMask ? (*mask)[i] : 0xFF;
|
||||
if ((maskByte & (*data_filter)[i]) != (maskByte & deviceData[i]))
|
||||
{
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMatch)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool filterDevice(UniversalBleScanResult scanResult)
|
||||
{
|
||||
bool hasNamePrefixFilter = !namePrefixFilter.empty();
|
||||
bool hasServiceFilter = !serviceFilterUUIDS.empty();
|
||||
bool hasManufacturerDataFilter = !manufacturerScanFilter.empty();
|
||||
|
||||
// If there is no filter at all, then allow device
|
||||
if (!hasNamePrefixFilter &&
|
||||
!hasServiceFilter &&
|
||||
!hasManufacturerDataFilter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check each filter condition
|
||||
return (hasNamePrefixFilter && isNameMatchingFilters(scanResult.name())) ||
|
||||
(hasServiceFilter && isServicesMatchingFilters(scanResult.services())) ||
|
||||
(hasManufacturerDataFilter && isManufacturerDataMatchingFilters(scanResult.manufacturer_data_list()));
|
||||
}
|
||||
|
||||
} // namespace universal_ble
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
#include "helper/universal_ble_base.h"
|
||||
#include "generated/universal_ble.g.h"
|
||||
|
||||
namespace universal_ble
|
||||
{
|
||||
void setScanFilter(const UniversalScanFilter filter);
|
||||
void resetScanFilter();
|
||||
bool filterDevice(UniversalBleScanResult scanResult);
|
||||
} // namespace universal_ble
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "helper/universal_enum.h"
|
||||
#include "generated/universal_ble.g.h"
|
||||
#include "pin_entry.h"
|
||||
#include "universal_ble_filter_util.h"
|
||||
|
||||
namespace universal_ble
|
||||
{
|
||||
@@ -32,7 +33,6 @@ namespace universal_ble
|
||||
|
||||
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
|
||||
std::vector<UniversalManufacturerDataFilter> manufacturerScanFilter = std::vector<UniversalManufacturerDataFilter>();
|
||||
|
||||
void UniversalBlePlugin::RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar)
|
||||
{
|
||||
@@ -107,30 +107,30 @@ namespace universal_ble
|
||||
{
|
||||
bluetoothLEWatcher = BluetoothLEAdvertisementWatcher();
|
||||
bluetoothLEWatcher.ScanningMode(BluetoothLEScanningMode::Active);
|
||||
resetScanFilter();
|
||||
|
||||
// reset scan filters
|
||||
manufacturerScanFilter.clear();
|
||||
if (filter != nullptr)
|
||||
{
|
||||
// Apply Services filter
|
||||
const auto &services = filter->with_services();
|
||||
if (!services.empty())
|
||||
// Only Services filter supported natively
|
||||
bool hasCustomFilters = filter->with_manufacturer_data().size() > 0 || filter->with_name_prefix().size() > 0;
|
||||
if (hasCustomFilters)
|
||||
{
|
||||
for (const auto &uuid : services)
|
||||
std::cout << "Using Custom Scan Filter" << std::endl;
|
||||
setScanFilter(*filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply Services filter
|
||||
if (!filter->with_services().empty())
|
||||
{
|
||||
std::string uuid_str = std::get<std::string>(uuid);
|
||||
bluetoothLEWatcher.AdvertisementFilter().Advertisement().ServiceUuids().Append(uuid_to_guid(uuid_str));
|
||||
for (const auto &uuid : filter->with_services())
|
||||
{
|
||||
bluetoothLEWatcher.AdvertisementFilter().Advertisement().ServiceUuids().Append(uuid_to_guid(std::get<std::string>(uuid)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
@@ -476,52 +476,6 @@ namespace universal_ble
|
||||
|
||||
/// Helper Methods
|
||||
|
||||
std::vector<uint8_t> parseManufacturerDataHead(BluetoothLEAdvertisement advertisement, std::string deviceId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (advertisement == nullptr)
|
||||
return {};
|
||||
|
||||
if (advertisement.ManufacturerData().Size() == 0)
|
||||
return {};
|
||||
|
||||
auto manufacturerData = advertisement.ManufacturerData().GetAt(0);
|
||||
if (manufacturerData == nullptr)
|
||||
return {};
|
||||
|
||||
uint16_t companyId = manufacturerData.CompanyId();
|
||||
uint8_t prefix[2];
|
||||
auto leastSignificantBit = static_cast<uint8_t>(companyId & 0xFF);
|
||||
auto mostSignificantBit = static_cast<uint8_t>(companyId >> 8);
|
||||
if (isLittleEndian())
|
||||
{
|
||||
prefix[0] = leastSignificantBit;
|
||||
prefix[1] = mostSignificantBit;
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix[0] = mostSignificantBit;
|
||||
prefix[1] = leastSignificantBit;
|
||||
}
|
||||
std::vector<uint8_t> result = {prefix[0], prefix[1]};
|
||||
|
||||
auto data = to_bytevc(manufacturerData.Data());
|
||||
result.insert(result.end(), data.begin(), data.end());
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cerr << "Error in parsing manufacturer data for device " << deviceId << ": " << e.what() << std::endl;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Unknown error occurred in parsing manufacturer data for device " << deviceId << std::endl;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
winrt::fire_and_forget UniversalBlePlugin::InitializeAsync()
|
||||
{
|
||||
auto radios = co_await Radio::GetRadiosAsync();
|
||||
@@ -653,11 +607,13 @@ namespace universal_ble
|
||||
scanResult.set_is_paired(currentScanResult.is_paired());
|
||||
shouldUpdate = true;
|
||||
}
|
||||
if (scanResult.manufacturer_data_head() == nullptr && currentScanResult.manufacturer_data_head() != nullptr)
|
||||
|
||||
if ((scanResult.manufacturer_data_list() == nullptr || scanResult.manufacturer_data_list()->empty()) && currentScanResult.manufacturer_data_list() != nullptr)
|
||||
{
|
||||
scanResult.set_manufacturer_data_head(currentScanResult.manufacturer_data_head());
|
||||
scanResult.set_manufacturer_data_list(currentScanResult.manufacturer_data_list());
|
||||
shouldUpdate = true;
|
||||
}
|
||||
|
||||
if (scanResult.services() == nullptr && currentScanResult.services() != nullptr)
|
||||
{
|
||||
scanResult.set_services(currentScanResult.services());
|
||||
@@ -677,7 +633,8 @@ namespace universal_ble
|
||||
scanResults.insert(std::make_pair(scanResult.device_id(), scanResult));
|
||||
}
|
||||
|
||||
if (isConnectable)
|
||||
// Filter final result before sending to Flutter
|
||||
if (isConnectable && filterDevice(scanResult))
|
||||
{
|
||||
uiThreadHandler_.Post([scanResult]
|
||||
{ callbackChannel->OnScanResult(scanResult, SuccessCallback, ErrorCallback); });
|
||||
@@ -789,21 +746,19 @@ namespace universal_ble
|
||||
{
|
||||
try
|
||||
{
|
||||
// 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());
|
||||
auto manufacturerData = parseManufacturerDataHead(args.Advertisement(), deviceId);
|
||||
|
||||
flutter::EncodableList manufacturerDataEncodableList = flutter::EncodableList();
|
||||
if (args.Advertisement() != nullptr)
|
||||
{
|
||||
for (BluetoothLEManufacturerData mfd : args.Advertisement().ManufacturerData())
|
||||
{
|
||||
UniversalManufacturerData universalManufacturerData = UniversalManufacturerData(static_cast<int64_t>(mfd.CompanyId()), to_bytevc(mfd.Data()));
|
||||
manufacturerDataEncodableList.push_back(flutter::CustomEncodableValue(universalManufacturerData));
|
||||
}
|
||||
}
|
||||
|
||||
auto dataSection = args.Advertisement().DataSections();
|
||||
for (auto &&data : dataSection)
|
||||
@@ -822,10 +777,14 @@ namespace universal_ble
|
||||
}
|
||||
|
||||
if (!name.empty())
|
||||
{
|
||||
universalScanResult.set_name(name);
|
||||
}
|
||||
|
||||
if (!manufacturerData.empty())
|
||||
universalScanResult.set_manufacturer_data_head(manufacturerData);
|
||||
if (!manufacturerDataEncodableList.empty())
|
||||
{
|
||||
universalScanResult.set_manufacturer_data_list(manufacturerDataEncodableList);
|
||||
}
|
||||
|
||||
universalScanResult.set_rssi(args.RawSignalStrengthInDBm());
|
||||
|
||||
@@ -853,6 +812,7 @@ namespace universal_ble
|
||||
universalScanResult.set_name(winrt::to_string(deviceInfo.Name()));
|
||||
}
|
||||
|
||||
// Filter Device
|
||||
pushUniversalScanResult(universalScanResult, args.IsConnectable());
|
||||
}
|
||||
catch (...)
|
||||
@@ -861,63 +821,6 @@ 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
|
||||
|
||||
@@ -124,7 +124,6 @@ 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);
|
||||
bool filterByManufacturerData(IVector<BluetoothLEManufacturerData> deviceManufactureData);
|
||||
winrt::fire_and_forget PairAsync(std::string device_id, std::function<void(ErrorOr<bool> reply)> result);
|
||||
winrt::fire_and_forget CustomPairAsync(std::string device_id, std::function<void(ErrorOr<bool> reply)> result);
|
||||
void PairingRequestedHandler(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs eventArgs);
|
||||
|
||||
Reference in New Issue
Block a user