Implement isScanning api (#188)
* Implement isScanning api * Improve IOS scanning api * Update Readme and Changelog
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
## 1.0.0
|
||||
* Unified error codes for all platforms
|
||||
* Add `isScanning` api
|
||||
|
||||
## 0.21.1
|
||||
* Fix device name resolution on Windows
|
||||
|
||||
@@ -80,6 +80,9 @@ UniversalBle.startScan(
|
||||
|
||||
// Stop scanning
|
||||
UniversalBle.stopScan();
|
||||
|
||||
// Check if scanning
|
||||
UniversalBle.isScanning();
|
||||
```
|
||||
|
||||
Before initiating a scan, ensure that Bluetooth is available:
|
||||
|
||||
@@ -36,6 +36,7 @@ class SafeScanner(private val bluetoothManager: BluetoothManager) {
|
||||
private val handler = Handler(Looper.myLooper()!!)
|
||||
private val startTimes = LinkedList<Long>()
|
||||
private var awaitingScan = false
|
||||
private var isScanning = false
|
||||
|
||||
fun startScan(filters: List<ScanFilter>, settings: ScanSettings, callback: ScanCallback) {
|
||||
val now = System.currentTimeMillis()
|
||||
@@ -52,7 +53,7 @@ class SafeScanner(private val bluetoothManager: BluetoothManager) {
|
||||
}
|
||||
|
||||
awaitingScan = true
|
||||
val delay = startTimes.first + EXCESSIVE_SCANNING_PERIOD_MS - now + 2_000
|
||||
val delay = startTimes.first() + EXCESSIVE_SCANNING_PERIOD_MS - now + 2_000
|
||||
Log.e(TAG, "startScan: too frequent, schedule auto-start after $delay ms $startTimes")
|
||||
|
||||
handler.postDelayed({
|
||||
@@ -65,8 +66,10 @@ class SafeScanner(private val bluetoothManager: BluetoothManager) {
|
||||
startTimes.addLast(now)
|
||||
try {
|
||||
bluetoothManager.adapter.bluetoothLeScanner?.startScan(filters, settings, callback)
|
||||
isScanning = true
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to start Scan : $e")
|
||||
isScanning = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,5 +78,10 @@ class SafeScanner(private val bluetoothManager: BluetoothManager) {
|
||||
awaitingScan = false
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
bluetoothManager.adapter.bluetoothLeScanner?.stopScan(callback)
|
||||
isScanning = false
|
||||
}
|
||||
|
||||
fun isScanning(): Boolean {
|
||||
return isScanning || awaitingScan
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Autogenerated from Pigeon (v26.0.5), do not edit directly.
|
||||
// Autogenerated from Pigeon (v26.1.0), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||
|
||||
@@ -447,6 +447,7 @@ interface UniversalBlePlatformChannel {
|
||||
fun disableBluetooth(callback: (Result<Boolean>) -> Unit)
|
||||
fun startScan(filter: UniversalScanFilter?)
|
||||
fun stopScan()
|
||||
fun isScanning(): Boolean
|
||||
fun connect(deviceId: String)
|
||||
fun disconnect(deviceId: String)
|
||||
fun setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Long, callback: (Result<Unit>) -> Unit)
|
||||
@@ -557,6 +558,21 @@ interface UniversalBlePlatformChannel {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isScanning$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { _, reply ->
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.isScanning())
|
||||
} catch (exception: Throwable) {
|
||||
UniversalBlePigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
|
||||
@@ -187,6 +187,10 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
||||
safeScanner.stopScan(scanCallback)
|
||||
}
|
||||
|
||||
override fun isScanning(): Boolean {
|
||||
return safeScanner.isScanning()
|
||||
}
|
||||
|
||||
override fun connect(deviceId: String) {
|
||||
// If already connected, send connected message,
|
||||
// if connecting, do nothing
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Autogenerated from Pigeon (v26.0.5), do not edit directly.
|
||||
// Autogenerated from Pigeon (v26.1.0), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
|
||||
import Foundation
|
||||
@@ -477,6 +477,7 @@ protocol UniversalBlePlatformChannel {
|
||||
func disableBluetooth(completion: @escaping (Result<Bool, Error>) -> Void)
|
||||
func startScan(filter: UniversalScanFilter?) throws
|
||||
func stopScan() throws
|
||||
func isScanning() throws -> Bool
|
||||
func connect(deviceId: String) throws
|
||||
func disconnect(deviceId: String) throws
|
||||
func setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
@@ -570,6 +571,19 @@ class UniversalBlePlatformChannelSetup {
|
||||
} else {
|
||||
stopScanChannel.setMessageHandler(nil)
|
||||
}
|
||||
let isScanningChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isScanning\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
isScanningChannel.setMessageHandler { _, reply in
|
||||
do {
|
||||
let result = try api.isScanning()
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isScanningChannel.setMessageHandler(nil)
|
||||
}
|
||||
let connectChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
connectChannel.setMessageHandler { message, reply in
|
||||
|
||||
@@ -39,6 +39,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
|
||||
private var characteristicWriteWithoutResponseFutures = [CharacteristicWriteFuture]()
|
||||
private var characteristicNotifyFutures = [CharacteristicNotifyFuture]()
|
||||
private var discoverServicesFutures = [DiscoverServicesFuture]()
|
||||
private var isManageScanning = false
|
||||
|
||||
init(callbackChannel: UniversalBleCallbackChannel) {
|
||||
self.callbackChannel = callbackChannel
|
||||
@@ -82,10 +83,31 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
|
||||
let options = [CBCentralManagerScanOptionAllowDuplicatesKey: true]
|
||||
|
||||
manager.scanForPeripherals(withServices: withServices, options: options)
|
||||
isManageScanning = true
|
||||
}
|
||||
|
||||
func stopScan() throws {
|
||||
manager.stopScan()
|
||||
isManageScanning = false
|
||||
}
|
||||
|
||||
func isScanning() throws -> Bool {
|
||||
var hasAuthorization = true
|
||||
#if os(iOS)
|
||||
if #available(iOS 13.1, *) {
|
||||
hasAuthorization = CBCentralManager.authorization == .allowedAlways
|
||||
} else {
|
||||
return isManageScanning
|
||||
}
|
||||
#elseif os(macOS)
|
||||
hasAuthorization = CBCentralManager.authorization == .allowedAlways
|
||||
#endif
|
||||
|
||||
if hasAuthorization {
|
||||
return manager.isScanning
|
||||
}
|
||||
|
||||
return isManageScanning
|
||||
}
|
||||
|
||||
func connect(deviceId: String) throws {
|
||||
|
||||
@@ -13,6 +13,7 @@ class MockUniversalBle extends UniversalBlePlatform {
|
||||
);
|
||||
|
||||
Uint8List _serviceValue = utf8.encode('Result');
|
||||
bool _isScanning = false;
|
||||
|
||||
final BleService _mockService = BleService('180', [
|
||||
BleCharacteristic('180A', [
|
||||
@@ -26,11 +27,20 @@ class MockUniversalBle extends UniversalBlePlatform {
|
||||
Future<void> startScan({
|
||||
ScanFilter? scanFilter,
|
||||
PlatformConfig? platformConfig,
|
||||
}) async =>
|
||||
updateScanResult(_mockBleDevice);
|
||||
}) async {
|
||||
_isScanning = true;
|
||||
updateScanResult(_mockBleDevice);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stopScan() async {}
|
||||
Future<void> stopScan() async {
|
||||
_isScanning = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isScanning() async {
|
||||
return _isScanning;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> connect(String deviceId, {Duration? connectionTimeout}) async {
|
||||
|
||||
@@ -63,6 +63,13 @@ class _MyAppState extends State<MyApp> {
|
||||
// UniversalBle.onQueueUpdate = (String id, int remainingItems) {
|
||||
// debugPrint("Queue: $id RemainingItems: $remainingItems");
|
||||
// };
|
||||
|
||||
UniversalBle.isScanning().then((value) {
|
||||
debugPrint("Is Scanning: $value");
|
||||
setState(() {
|
||||
_isScanning = value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void trackAvailabilityState() {
|
||||
|
||||
@@ -20,9 +20,9 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/universal_ble/darwin
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
device_info_plus: b0fafc687fb901e2af612763340f1b0d4352f8e5
|
||||
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
universal_ble: ff19787898040d721109c6324472e5dd4bc86adc
|
||||
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
|
||||
|
||||
PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3
|
||||
|
||||
|
||||
@@ -88,6 +88,14 @@ class UniversalBle {
|
||||
);
|
||||
}
|
||||
|
||||
/// Check if currently scanning for devices.
|
||||
/// Returns `true` if scanning is active, `false` otherwise.
|
||||
static Future<bool> isScanning() async {
|
||||
return await _bleCommandQueue.queueCommand(
|
||||
() => _platform.isScanning(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Connect to a device.
|
||||
/// It is advised to stop scanning before connecting.
|
||||
/// It throws error if device connection fails.
|
||||
|
||||
@@ -125,6 +125,12 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isScanning() async {
|
||||
await _ensureInitialized();
|
||||
return _activeAdapter?.discovering == true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BleConnectionState> getConnectionState(String deviceId) async {
|
||||
BlueZDevice? device = _devices[deviceId] ??
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Autogenerated from Pigeon (v26.0.5), do not edit directly.
|
||||
// Autogenerated from Pigeon (v26.1.0), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
|
||||
|
||||
@@ -633,6 +633,36 @@ class UniversalBlePlatformChannel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> isScanning() async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isScanning$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel =
|
||||
BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||
final List<Object?>? pigeonVar_replyList =
|
||||
await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else if (pigeonVar_replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (pigeonVar_replyList[0] as bool?)!;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> connect(String deviceId) async {
|
||||
final String pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect$pigeonVar_messageChannelSuffix';
|
||||
|
||||
@@ -57,6 +57,10 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
|
||||
Future<void> stopScan() =>
|
||||
_executeWithErrorHandling(() => _channel.stopScan());
|
||||
|
||||
@override
|
||||
Future<bool> isScanning() =>
|
||||
_executeWithErrorHandling(() => _channel.isScanning());
|
||||
|
||||
@override
|
||||
Future<BleConnectionState> getConnectionState(String deviceId) async {
|
||||
int state = await _executeWithErrorHandling(
|
||||
|
||||
@@ -43,6 +43,8 @@ abstract class UniversalBlePlatform {
|
||||
|
||||
Future<void> stopScan();
|
||||
|
||||
Future<bool> isScanning();
|
||||
|
||||
Future<void> connect(String deviceId, {Duration? connectionTimeout});
|
||||
|
||||
Future<void> disconnect(String deviceId);
|
||||
|
||||
@@ -20,6 +20,7 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
final Map<String, StreamSubscription> _connectedDeviceStreamList = {};
|
||||
final Map<String, StreamSubscription> _characteristicStreamList = {};
|
||||
final Map<String, List<_UniversalWebBluetoothService>> _serviceCache = {};
|
||||
bool _isScanning = false;
|
||||
|
||||
@override
|
||||
Future<BleConnectionState> getConnectionState(String deviceId) async {
|
||||
@@ -83,6 +84,7 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
PlatformConfig? platformConfig,
|
||||
}) async {
|
||||
try {
|
||||
_isScanning = true;
|
||||
FlutterWebBluetooth.instance.isAvailable;
|
||||
BluetoothDevice device = await FlutterWebBluetooth.instance.requestDevice(
|
||||
_getRequestOptionBuilder(scanFilter, platformConfig?.web),
|
||||
@@ -101,6 +103,8 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
throw WebBluetoothGloballyDisabled(message: error);
|
||||
}
|
||||
rethrow;
|
||||
} finally {
|
||||
_isScanning = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +150,9 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
||||
_disposeAdvertisementWatcher();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isScanning() async => _isScanning;
|
||||
|
||||
@override
|
||||
Future<void> setNotifiable(
|
||||
String deviceId,
|
||||
|
||||
@@ -34,6 +34,8 @@ abstract class UniversalBlePlatformChannel {
|
||||
|
||||
void stopScan();
|
||||
|
||||
bool isScanning();
|
||||
|
||||
void connect(String deviceId);
|
||||
|
||||
void disconnect(String deviceId);
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
pigeon: ^26.0.5
|
||||
pigeon: ^26.1.0
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
|
||||
@@ -95,4 +95,9 @@ abstract class UniversalBlePlatformMock extends UniversalBlePlatform {
|
||||
BleOutputProperty bleOutputProperty) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isScanning() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Autogenerated from Pigeon (v26.0.5), do not edit directly.
|
||||
// Autogenerated from Pigeon (v26.1.0), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
|
||||
#undef _HAS_EXCEPTIONS
|
||||
@@ -623,6 +623,27 @@ void UniversalBlePlatformChannel::SetUp(
|
||||
channel.SetMessageHandler(nullptr);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.isScanning" + prepended_suffix, &GetCodec());
|
||||
if (api != nullptr) {
|
||||
channel.SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply<EncodableValue>& reply) {
|
||||
try {
|
||||
ErrorOr<bool> output = api->IsScanning();
|
||||
if (output.has_error()) {
|
||||
reply(WrapError(output.error()));
|
||||
return;
|
||||
}
|
||||
EncodableList wrapped;
|
||||
wrapped.push_back(EncodableValue(std::move(output).TakeValue()));
|
||||
reply(EncodableValue(std::move(wrapped)));
|
||||
} catch (const std::exception& exception) {
|
||||
reply(WrapError(exception.what()));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
channel.SetMessageHandler(nullptr);
|
||||
}
|
||||
}
|
||||
{
|
||||
BasicMessageChannel<> channel(binary_messenger, "dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect" + prepended_suffix, &GetCodec());
|
||||
if (api != nullptr) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Autogenerated from Pigeon (v26.0.5), do not edit directly.
|
||||
// Autogenerated from Pigeon (v26.1.0), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
|
||||
#ifndef PIGEON_UNIVERSAL_BLE_G_H_
|
||||
@@ -352,6 +352,7 @@ class UniversalBlePlatformChannel {
|
||||
virtual void DisableBluetooth(std::function<void(ErrorOr<bool> reply)> result) = 0;
|
||||
virtual std::optional<FlutterError> StartScan(const UniversalScanFilter* filter) = 0;
|
||||
virtual std::optional<FlutterError> StopScan() = 0;
|
||||
virtual ErrorOr<bool> IsScanning() = 0;
|
||||
virtual std::optional<FlutterError> Connect(const std::string& device_id) = 0;
|
||||
virtual std::optional<FlutterError> Disconnect(const std::string& device_id) = 0;
|
||||
virtual void SetNotifiable(
|
||||
|
||||
@@ -212,6 +212,15 @@ std::optional<FlutterError> UniversalBlePlugin::StopScan() {
|
||||
}
|
||||
};
|
||||
|
||||
ErrorOr<bool> UniversalBlePlugin::IsScanning() {
|
||||
if (bluetooth_le_watcher_ != nullptr) {
|
||||
try {
|
||||
return bluetooth_le_watcher_.Status() == BluetoothLEAdvertisementWatcherStatus::Started;
|
||||
} catch (...) {}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ErrorOr<int64_t>
|
||||
UniversalBlePlugin::GetConnectionState(const std::string &device_id) {
|
||||
const auto it = connected_devices_.find(str_to_mac_address(device_id));
|
||||
|
||||
@@ -158,6 +158,7 @@ namespace universal_ble
|
||||
ErrorOr<int64_t> GetConnectionState(const std::string &device_id) override;
|
||||
std::optional<FlutterError> StartScan(const UniversalScanFilter *filter) override;
|
||||
std::optional<FlutterError> StopScan() override;
|
||||
ErrorOr<bool> IsScanning() override;
|
||||
std::optional<FlutterError> Connect(const std::string &device_id) override;
|
||||
std::optional<FlutterError> Disconnect(const std::string &device_id) override;
|
||||
void DiscoverServices(
|
||||
|
||||
Reference in New Issue
Block a user