startScan static method
Start a scan, and return a stream of results
timeout
calls stopScan after a specified durationremoveIfGone
if true, remove devices after they've stopped advertising for X durationoneByOne
if true, we will stream every advertistment one by one, including duplicates. If false, we deduplicate the advertisements, and return a list of devices.androidUsesFineLocation
request ACCESS_FINE_LOCATION permission at runtime
Implementation
static Future<void> startScan({
List<Guid> withServices = const [],
Duration? timeout,
Duration? removeIfGone,
bool oneByOne = false,
bool androidUsesFineLocation = false,
}) async {
await _initialize();
// stop existing scan
if (_isScanning.latestValue == true) {
await stopScan();
}
// push to stream
_isScanning.add(true);
// Start timer *after* stream is being listened to, to make sure the
// timeout does not fire before _buffer is set
if (timeout != null) {
_scanTimeout = Timer(timeout, stopScan);
}
/// remove connection by OS.
/// The reason why we add this logic is
/// to avoid uncontrollable devices and to make consistency.
/// add WinBle scanning
WinBle.startScanning();
// check every 250ms for gone devices?
late Stream<BleDevice?> outputStream;
if (removeIfGone != null) {
outputStream = _mergeStreams(
[WinBle.scanStream, Stream.periodic(Duration(milliseconds: 250))],
);
} else {
outputStream = WinBle.scanStream;
}
final output = <ScanResult>[];
// listen & push to `scanResults` stream
_scanSubscription = outputStream.listen(
(BleDevice? winBleDevice) {
if (winBleDevice == null) {
// if null, this is just a periodic update for removing old results
output.removeWhere((elm) =>
DateTime.now().difference(elm.timeStamp) > removeIfGone!);
// push to stream
_scanResultsList.add(List.from(output));
} else {
final remoteId = DeviceIdentifier(winBleDevice.address.toUpperCase());
final existedName = output
.where((sr) => sr.device.remoteId == remoteId)
.firstOrNull
?.device
.platformName;
final deviceName = winBleDevice.name.isNotEmpty
? winBleDevice.name
: existedName ?? '';
final device = BluetoothDeviceWindows(
platformName: deviceName,
remoteId: remoteId,
rssi: int.tryParse(winBleDevice.rssi) ?? -100,
);
final sr = ScanResult(
device: device,
advertisementData: AdvertisementData(
advName: deviceName,
txPowerLevel: winBleDevice.adStructures
?.where((e) => e.type == 10)
.singleOrNull
?.data
.firstOrNull,
//TODO: Should verify
connectable: !winBleDevice.advType.contains('Non'),
manufacturerData: {
if (winBleDevice.manufacturerData.length >= 2)
winBleDevice.manufacturerData[0]:
winBleDevice.manufacturerData.sublist(2),
},
//TODO: implementation missing
serviceData: {},
serviceUuids: winBleDevice.serviceUuids
.map((e) =>
Guid((e as String).replaceAll(RegExp(r'[{}]'), '')))
.toList(),
appearance: null,
),
rssi: int.tryParse(winBleDevice.rssi) ?? -100,
timeStamp: DateTime.now(),
);
// add result to output
if (oneByOne) {
output
..clear()
..add(sr);
} else {
output.addOrUpdate(sr);
}
// push to stream
_scanResultsList.add(List.from(output));
}
},
);
}