From 35d4737f3aadf4cfdcb14c01036725a27a2a68e4 Mon Sep 17 00:00:00 2001 From: Rohit Sangwan Date: Thu, 15 Feb 2024 12:27:11 +0530 Subject: [PATCH] Implement queue (#11) * Implement queue * Enable queue by default, remove queue from web and update readme * Rename useQueue to queuesRequests * Rename queuesRequests and improve readme * Rename _executeMethod and improve readme * Rename to _isQueueEnabled --------- Co-authored-by: Foti Dim --- .vscode/launch.json | 4 +- CHANGELOG.md | 4 + README.md | 26 ++- example/lib/data/mock_universal_ble.dart | 108 +++++++++++++ example/lib/home/home.dart | 21 +++ lib/src/ble_command_queue.dart | 80 ++++++++++ lib/src/universal_ble.dart | 148 ++++++++++++------ lib/src/universal_ble_platform_interface.dart | 14 ++ lib/src/universal_ble_web/queue.dart | 139 ---------------- .../universal_ble_web/universal_ble_web.dart | 61 ++++---- lib/universal_ble.dart | 3 +- pubspec.yaml | 2 +- 12 files changed, 387 insertions(+), 223 deletions(-) create mode 100644 example/lib/data/mock_universal_ble.dart create mode 100644 lib/src/ble_command_queue.dart delete mode 100644 lib/src/universal_ble_web/queue.dart diff --git a/.vscode/launch.json b/.vscode/launch.json index ac15806..f80b559 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,13 +11,13 @@ "type": "dart" }, { - "name": "example_auto_connect", + "name": "example_mock", "cwd": "example", "request": "launch", "type": "dart", "args": [ "--dart-define", - "AUTO_CONNECT=true", + "MOCK=true", ] } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c9f31..742a93d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.2 +* Add command queue +* Improve error handling on Android and Apple + ## 0.9.1 * Improve logging diff --git a/README.md b/README.md index 1fede3e..b3a35db 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE - [Reading & Writing data](#reading--writing-data) - [Pairing](#pairing) - [Bluetooth Availability](#bluetooth-availability) +- [Command Queue](#command-queue) +- [Timeout](#timeout) ### API Support Matrix @@ -147,6 +149,28 @@ UniversalBle.onAvailabilityChange = (state) { UniversalBle.enableBluetooth(); ``` +## Command Queue + +By default, all commands will be executed in a queue. Each command will wait for the previous one to finish. +Some platforms (e.g. Android) will fail to send consecutive commands without any delay between them so it is a good idea to leave to queue enabled. + +```dart +// Disable queue +UniversalBle.queuesCommands = false; +``` + +## Timeout + +By default, all commands have a timeout of 10 seconds. + +```dart +// Change timeout +UniversalBle.timeout = const Duration(seconds: 10); + +// Disable timeout +UniversalBle.timeout = null; +``` + ## Platform-Specific Setup ### Android @@ -191,7 +215,7 @@ UniversalBle.startScan( ```dart // Create a class that extends UniversalBlePlatform class UniversalBleMock extends UniversalBlePlatform { - // Implement all methods + // Implement all commands } UniversalBle.setInstance(UniversalBleMock()); diff --git a/example/lib/data/mock_universal_ble.dart b/example/lib/data/mock_universal_ble.dart new file mode 100644 index 0000000..44514cb --- /dev/null +++ b/example/lib/data/mock_universal_ble.dart @@ -0,0 +1,108 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:universal_ble/universal_ble.dart'; + +/// Mock implementation of [UniversalBlePlatform] for testing +class MockUniversalBle extends UniversalBlePlatform { + final _mockBleScanResult = BleScanResult( + name: 'MockDevice', + deviceId: 'MockDeviceId', + rssi: 50, + manufacturerData: Uint8List(0), + ); + + Uint8List _serviceValue = utf8.encode('Result'); + + final BleService _mockService = BleService('180', [ + BleCharacteristic('180A', [ + CharacteristicProperty.read, + CharacteristicProperty.write, + CharacteristicProperty.notify, + ]), + ]); + + @override + Future startScan({WebRequestOptionsBuilder? webRequestOptions}) async { + onScanResult?.call(_mockBleScanResult); + } + + @override + Future stopScan() async {} + + @override + Future connect(String deviceId, {Duration? connectionTimeout}) async { + onConnectionChanged?.call(deviceId, BleConnectionState.connected); + } + + @override + Future disconnect(String deviceId) async { + onConnectionChanged?.call(deviceId, BleConnectionState.disconnected); + } + + @override + Future> discoverServices(String deviceId) async { + return [_mockService]; + } + + @override + Future enableBluetooth() async { + await Future.delayed(const Duration(milliseconds: 500)); + return true; + } + + @override + Future getBluetoothAvailabilityState() async { + return AvailabilityState.poweredOn; + } + + @override + Future> getConnectedDevices( + List? withServices) async { + return []; + } + + @override + Future readValue( + String deviceId, String service, String characteristic) async { + await Future.delayed(const Duration(milliseconds: 500)); + return _serviceValue; + } + + @override + Future writeValue( + String deviceId, + String service, + String characteristic, + Uint8List value, + BleOutputProperty bleOutputProperty) async { + await Future.delayed(const Duration(milliseconds: 500)); + _serviceValue = value; + } + + @override + Future requestMtu(String deviceId, int expectedMtu) async { + await Future.delayed(const Duration(seconds: 1)); + return 512; + } + + @override + Future setNotifiable(String deviceId, String service, + String characteristic, BleInputProperty bleInputProperty) async {} + + @override + Future isPaired(String deviceId) async { + await Future.delayed(const Duration(milliseconds: 500)); + return true; + } + + @override + Future pair(String deviceId) async { + onPairingStateChange?.call(deviceId, true, null); + } + + @override + Future unPair(String deviceId) async { + onPairingStateChange?.call(deviceId, false, null); + } +} diff --git a/example/lib/home/home.dart b/example/lib/home/home.dart index 568bc49..6d25158 100644 --- a/example/lib/home/home.dart +++ b/example/lib/home/home.dart @@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:universal_ble/universal_ble.dart'; import 'package:universal_ble_example/data/capabilities.dart'; +import 'package:universal_ble_example/data/mock_universal_ble.dart'; import 'package:universal_ble_example/home/widgets/scanned_devices_placeholder_widget.dart'; import 'package:universal_ble_example/home/widgets/scanned_item_widget.dart'; import 'package:universal_ble_example/data/permission_handler.dart'; @@ -21,6 +22,8 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { final _scanResults = []; bool _isScanning = false; + bool _isQueueEnabled = true; + AvailabilityState? bleAvailabilityState; late WebRequestOptionsBuilder _requestOptions; final List _services = [ @@ -36,6 +39,15 @@ class _MyAppState extends State { void initState() { super.initState(); + /// Set mock instance for testing + if (const bool.fromEnvironment('MOCK')) { + UniversalBle.setInstance(MockUniversalBle()); + } + + /// Setup queue and timeout + UniversalBle.queuesCommands = _isQueueEnabled; + UniversalBle.timeout = const Duration(seconds: 10); + /// Add common services for web if (kIsWeb) { _services.addAll(WebRequestOptionsBuilder.defaultServices); @@ -162,6 +174,15 @@ class _MyAppState extends State { }); }, ), + PlatformButton( + text: _isQueueEnabled ? 'Disable Queue' : 'Enable Queue', + onPressed: () { + setState(() { + _isQueueEnabled = !_isQueueEnabled; + UniversalBle.queuesCommands = _isQueueEnabled; + }); + }, + ), if (_scanResults.isNotEmpty) PlatformButton( text: 'Clear List', diff --git a/lib/src/ble_command_queue.dart b/lib/src/ble_command_queue.dart new file mode 100644 index 0000000..b2f5f12 --- /dev/null +++ b/lib/src/ble_command_queue.dart @@ -0,0 +1,80 @@ +import 'dart:async'; + +/// Original Author: Ryan Knell (https://github.com/rknell/dart_queue) + +/// Queue to execute Futures in order. +/// It awaits each future before executing the next one. +class BleCommandQueue { + final Set _activeItems = {}; + int _lastProcessId = 0; + bool _isCancelled = false; + final List<_QueuedFuture> _nextCycle = []; + + Future add(Future Function() closure, {Duration? timeout}) { + if (_isCancelled) throw Exception('Queue Cancelled'); + final completer = Completer(); + _nextCycle.add(_QueuedFuture(closure, completer, timeout)); + _updateRemainingItems(); + if (_activeItems.isEmpty) _queueUpNext(); + return completer.future; + } + + void dispose() { + for (final item in _nextCycle) { + item.completer.completeError(Exception('Queue Cancelled')); + } + _nextCycle.removeWhere((item) => item.completer.isCompleted); + _isCancelled = true; + } + + void _queueUpNext() { + if (_nextCycle.isNotEmpty && !_isCancelled && _activeItems.length <= 1) { + final processId = _lastProcessId; + _activeItems.add(processId); + final item = _nextCycle.first; + _lastProcessId++; + _nextCycle.remove(item); + item.onComplete = () async { + _activeItems.remove(processId); + _updateRemainingItems(); + _queueUpNext(); + }; + unawaited(item.execute()); + } + } + + void _updateRemainingItems() { + // int remainingQueueItems = _nextCycle.length + _activeItems.length; + // onRemainingItemsUpdate?.call(_); + } +} + +class _QueuedFuture { + final Completer completer; + final Future Function() closure; + Function? onComplete; + final Duration? timeout; + + _QueuedFuture(this.closure, this.completer, this.timeout, {this.onComplete}); + + Future execute() async { + try { + T result; + if (timeout != null) { + result = await closure().timeout(timeout!); + } else { + result = await closure(); + } + if (result != null) { + completer.complete(result); + } else { + completer.complete(null); + } + await Future.microtask(() {}); + } catch (e, stack) { + completer.completeError(e, stack); + } finally { + if (onComplete != null) onComplete?.call(); + } + } +} diff --git a/lib/src/universal_ble.dart b/lib/src/universal_ble.dart index c1a0e31..15abd44 100644 --- a/lib/src/universal_ble.dart +++ b/lib/src/universal_ble.dart @@ -1,4 +1,4 @@ -import 'dart:io'; +import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:universal_ble/src/universal_ble_linux/universal_ble_linux.dart'; @@ -14,10 +14,30 @@ class UniversalBle { static void setInstance(UniversalBlePlatform instance) => _platform = instance; + /// Set global timeout for all commands + static Duration? timeout = const Duration(seconds: 10); + + static BleCommandQueue? _queue = BleCommandQueue(); + + /// Setup global queue for all commands, by default queue is enabled + static set queuesCommands(bool value) { + if (value) { + _queue ??= BleCommandQueue(); + UniversalBlePlatform.logInfo('Queue enabled'); + } else { + _queue?.dispose(); + _queue = null; + UniversalBlePlatform.logInfo('Queue disabled'); + } + } + /// To get Bluetooth state availability /// To get updates, set [onAvailabilityChange] listener static Future getBluetoothAvailabilityState() async { - return await _platform.getBluetoothAvailabilityState(); + return await _executeCommand( + () => _platform.getBluetoothAvailabilityState(), + timeout: timeout, + ); } /// To Start scan, get scan results in [onScanResult] listener @@ -26,13 +46,19 @@ class UniversalBle { static Future startScan({ WebRequestOptionsBuilder? webRequestOptions, }) async { - await _platform.startScan(webRequestOptions: webRequestOptions); + return await _executeCommand( + () => _platform.startScan(webRequestOptions: webRequestOptions), + timeout: null, + ); } /// To Stop scan, set [onScanResult] listener to `null` if you don't need it anymore /// might throw errors if Bluetooth is not available static Future stopScan() async { - await _platform.stopScan(); + return await _executeCommand( + () => _platform.stopScan(), + timeout: null, + ); } /// To connect to a device, get connection state in [onConnectionChanged] listener @@ -43,17 +69,26 @@ class UniversalBle { String deviceId, { Duration? connectionTimeout, }) async { - await _platform.connect(deviceId, connectionTimeout: connectionTimeout); + return await _executeCommand( + () => _platform.connect(deviceId, connectionTimeout: connectionTimeout), + timeout: timeout, + ); } /// To disconnect from a device, get connection state in [onConnectionChanged] listener static Future disconnect(String deviceId) async { - await _platform.disconnect(deviceId); + return await _executeCommand( + () => _platform.disconnect(deviceId), + timeout: timeout, + ); } /// To discover services of a device static Future> discoverServices(String deviceId) async { - return await _platform.discoverServices(deviceId); + return await _executeCommand( + () => _platform.discoverServices(deviceId), + timeout: timeout, + ); } /// To set a characteristic notifiable, set `bleInputProperty` to [BleInputProperty.notification] or [BleInputProperty.indication], get updates in [onValueChanged] listener @@ -64,22 +99,28 @@ class UniversalBle { String characteristic, BleInputProperty bleInputProperty, ) async { - await _platform.setNotifiable( - deviceId, - service, - characteristic, - bleInputProperty, + return await _executeCommand( + () => _platform.setNotifiable( + deviceId, + service, + characteristic, + bleInputProperty, + ), + timeout: timeout, ); } /// To read a characteristic value - /// on iOS and MacOS, this method will also trigger [onValueChanged] listener + /// on iOS and MacOS, this command will also trigger [onValueChanged] listener static Future readValue( String deviceId, String service, String characteristic, ) async { - return await _platform.readValue(deviceId, service, characteristic); + return await _executeCommand( + () => _platform.readValue(deviceId, service, characteristic), + timeout: timeout, + ); } /// To write a characteristic value @@ -91,33 +132,50 @@ class UniversalBle { Uint8List value, BleOutputProperty bleOutputProperty, ) async { - await _platform.writeValue( - deviceId, - service, - characteristic, - value, - bleOutputProperty, + await _executeCommand( + () => _platform.writeValue( + deviceId, + service, + characteristic, + value, + bleOutputProperty, + ), + timeout: timeout, ); } /// `requestMtu` not supported on `Linux` and `Web static Future requestMtu(String deviceId, int expectedMtu) async { - return await _platform.requestMtu(deviceId, expectedMtu); + return await _executeCommand( + () => _platform.requestMtu(deviceId, expectedMtu), + timeout: timeout, + ); } - /// Pair methods are not supported on `iOS`, `MacOS` and `Web` + /// Pair commands are not supported on `iOS`, `MacOS` and `Web` static Future isPaired(String deviceId) async { - return await _platform.isPaired(deviceId); + return await _executeCommand( + () => _platform.isPaired(deviceId), + timeout: timeout, + ); } - /// To trigger pair request, might throw errors if device is already paired + /// To trigger pair request + /// might throw errors if device is already paired static Future pair(String deviceId) async { - await _platform.pair(deviceId); + return await _executeCommand( + () => _platform.pair(deviceId), + timeout: timeout, + ); } - /// To trigger unPair request, might throw errors if device is not paired + /// To trigger unPair request + /// might throw errors if device is not paired static Future unPair(String deviceId) async { - await _platform.unPair(deviceId); + return await _executeCommand( + () => _platform.unPair(deviceId), + timeout: timeout, + ); } /// To get connected devices to the system ( connected by any app ) @@ -128,13 +186,19 @@ class UniversalBle { static Future> getConnectedDevices({ List? withServices, }) async { - return await _platform.getConnectedDevices(withServices); + return await _executeCommand( + () => _platform.getConnectedDevices(withServices), + timeout: timeout, + ); } /// Enabling Bluetooth, might throw errors if Bluetooth is not available /// Not supported on `Web` and `Apple` static Future enableBluetooth() async { - return await _platform.enableBluetooth(); + return await _executeCommand( + () => _platform.enableBluetooth(), + timeout: timeout, + ); } /// To get Bluetooth state availability @@ -165,21 +229,17 @@ class UniversalBle { static UniversalBlePlatform _defaultPlatform() { if (kIsWeb) return UniversalBleWeb.instance; - if (Platform.isLinux) return UniversalBleLinux.instance; + if (defaultTargetPlatform == TargetPlatform.linux) { + return UniversalBleLinux.instance; + } return UniversalBlePigeonChannel.instance; } + + static Future _executeCommand( + Future Function() command, { + required Duration? timeout, + }) { + return _queue?.add(command, timeout: timeout) ?? + (timeout != null ? command().timeout(timeout) : command()); + } } - -// Callback types -typedef OnConnectionChanged = void Function( - String deviceId, BleConnectionState state); - -typedef OnValueChanged = void Function( - String deviceId, String characteristicId, Uint8List value); - -typedef OnScanResult = void Function(BleScanResult scanResult); - -typedef OnAvailabilityChange = void Function(AvailabilityState state); - -typedef OnPairingStateChange = void Function( - String deviceId, bool isPaired, String? error); diff --git a/lib/src/universal_ble_platform_interface.dart b/lib/src/universal_ble_platform_interface.dart index a8deee4..fa30ac8 100644 --- a/lib/src/universal_ble_platform_interface.dart +++ b/lib/src/universal_ble_platform_interface.dart @@ -56,3 +56,17 @@ abstract class UniversalBlePlatform { log(message, name: 'UniversalBle'); } } + +// Callback types +typedef OnConnectionChanged = void Function( + String deviceId, BleConnectionState state); + +typedef OnValueChanged = void Function( + String deviceId, String characteristicId, Uint8List value); + +typedef OnScanResult = void Function(BleScanResult scanResult); + +typedef OnAvailabilityChange = void Function(AvailabilityState state); + +typedef OnPairingStateChange = void Function( + String deviceId, bool isPaired, String? error); diff --git a/lib/src/universal_ble_web/queue.dart b/lib/src/universal_ble_web/queue.dart deleted file mode 100644 index 24aa133..0000000 --- a/lib/src/universal_ble_web/queue.dart +++ /dev/null @@ -1,139 +0,0 @@ -import 'dart:async'; - -/// Original Author: Ryan Knell (https://github.com/rknell/dart_queue/commits/master/lib/src/dart_queue_base.dart) - -/// Queue to execute Futures in order. -/// It awaits each future before executing the next one. -class Queue { - final List<_QueuedFuture> _nextCycle = []; - final Duration? delay; - final Duration? timeout; - int parallel; - int _lastProcessId = 0; - bool _isCancelled = false; - bool get isCancelled => _isCancelled; - StreamController? _remainingItemsController; - - Stream? get remainingItems { - _remainingItemsController ??= StreamController(); - return _remainingItemsController?.stream.asBroadcastStream(); - } - - final List> _completeListeners = []; - Future get onComplete { - final completer = Completer(); - _completeListeners.add(completer); - return completer.future; - } - - Set activeItems = {}; - - void cancel() { - for (final item in _nextCycle) { - item.completer.completeError(QueueCancelledException()); - } - _nextCycle.removeWhere((item) => item.completer.isCompleted); - _isCancelled = true; - } - - void dispose() { - _remainingItemsController?.close(); - cancel(); - } - - Queue({this.delay, this.parallel = 1, this.timeout}); - - Future add(Future Function() closure) { - if (isCancelled) throw QueueCancelledException(); - final completer = Completer(); - _nextCycle.add(_QueuedFuture(closure, completer, timeout)); - _updateRemainingItems(); - unawaited(_process()); - return completer.future; - } - - Future _process() async { - if (activeItems.length < parallel) { - _queueUpNext(); - } - } - - void _updateRemainingItems() { - final remainingItemsController = _remainingItemsController; - if (remainingItemsController != null && - remainingItemsController.isClosed == false) { - remainingItemsController.sink.add(_nextCycle.length + activeItems.length); - } - } - - void _queueUpNext() { - if (_nextCycle.isNotEmpty && - !isCancelled && - activeItems.length <= parallel) { - final processId = _lastProcessId; - activeItems.add(processId); - final item = _nextCycle.first; - _lastProcessId++; - _nextCycle.remove(item); - item.onComplete = () async { - activeItems.remove(processId); - var completionDelay = delay; - if (completionDelay != null) { - await Future.delayed(completionDelay); - } - _updateRemainingItems(); - _queueUpNext(); - }; - unawaited(item.execute()); - } else if (activeItems.isEmpty && _nextCycle.isEmpty) { - for (final completer in _completeListeners) { - if (completer.isCompleted != true) { - completer.complete(); - } - } - _completeListeners.clear(); - } - } -} - -class QueueCancelledException implements Exception {} - -class _QueuedFuture { - final Completer completer; - final Future Function() closure; - final Duration? timeout; - Function? onComplete; - - _QueuedFuture(this.closure, this.completer, this.timeout, {this.onComplete}); - - bool _timedOut = false; - - Future execute() async { - try { - T result; - Timer? timeoutTimer; - - var executionTimeout = timeout; - if (executionTimeout != null) { - timeoutTimer = Timer(executionTimeout, () { - _timedOut = true; - if (onComplete != null) { - onComplete?.call(); - } - }); - } - result = await closure(); - if (result != null) { - completer.complete(result); - } else { - completer.complete(null); - } - timeoutTimer?.cancel(); - await Future.microtask(() {}); - } catch (e) { - completer.completeError(e); - } finally { - if (onComplete != null && !_timedOut) onComplete?.call(); - } - } -} diff --git a/lib/src/universal_ble_web/universal_ble_web.dart b/lib/src/universal_ble_web/universal_ble_web.dart index e265cd1..82c92e7 100644 --- a/lib/src/universal_ble_web/universal_ble_web.dart +++ b/lib/src/universal_ble_web/universal_ble_web.dart @@ -5,7 +5,6 @@ import 'dart:typed_data'; 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'; -import 'package:universal_ble/src/universal_ble_web/queue.dart'; class UniversalBleWeb extends UniversalBlePlatform { static UniversalBleWeb? _instance; @@ -15,7 +14,6 @@ class UniversalBleWeb extends UniversalBlePlatform { _setupListeners(); } - late final _queue = Queue(); final Map _bluetoothDeviceList = {}; final Map _deviceAdvertisementStreamList = {}; final Map _connectedDeviceStreamList = {}; @@ -222,27 +220,24 @@ class UniversalBleWeb extends UniversalBlePlatform { Uint8List value, BleOutputProperty bleOutputProperty, ) async { - await _queue.add(() async { - final bleCharacteristic = await _getBleCharacteristic( - deviceId: deviceId, - serviceId: service, - characteristicId: characteristic, + final bleCharacteristic = await _getBleCharacteristic( + deviceId: deviceId, + serviceId: service, + characteristicId: characteristic, + ); + + if (bleCharacteristic == null) { + throw Exception( + 'Characteristic $characteristic for service $service not found', ); + } - if (bleCharacteristic == null) { - throw Exception( - 'Characteristic $characteristic for service $service not found', - ); - } - - if (bleOutputProperty == BleOutputProperty.withResponse) { - await bleCharacteristic - .writeValueWithResponse(Uint8List.fromList(value)); - } else { - await bleCharacteristic - .writeValueWithoutResponse(Uint8List.fromList(value)); - } - }); + if (bleOutputProperty == BleOutputProperty.withResponse) { + await bleCharacteristic.writeValueWithResponse(Uint8List.fromList(value)); + } else { + await bleCharacteristic + .writeValueWithoutResponse(Uint8List.fromList(value)); + } } @override @@ -251,19 +246,17 @@ class UniversalBleWeb extends UniversalBlePlatform { String service, String characteristic, ) async { - return _queue.add(() async { - var bleCharacteristic = await _getBleCharacteristic( - deviceId: deviceId, - serviceId: service, - characteristicId: characteristic, - ); - if (bleCharacteristic == null) { - throw Exception( - 'Characteristic $characteristic for service $service not found'); - } - var data = await bleCharacteristic.readValue(); - return data.buffer.asUint8List(); - }); + var bleCharacteristic = await _getBleCharacteristic( + deviceId: deviceId, + serviceId: service, + characteristicId: characteristic, + ); + if (bleCharacteristic == null) { + throw Exception( + 'Characteristic $characteristic for service $service not found'); + } + var data = await bleCharacteristic.readValue(); + return data.buffer.asUint8List(); } /// `Unimplemented` diff --git a/lib/universal_ble.dart b/lib/universal_ble.dart index 794e6f3..bc868dd 100644 --- a/lib/universal_ble.dart +++ b/lib/universal_ble.dart @@ -1,7 +1,6 @@ library universal_ble; +export 'package:universal_ble/src/ble_command_queue.dart'; export 'package:universal_ble/src/universal_ble_platform_interface.dart'; export 'package:universal_ble/src/universal_ble.dart'; export 'package:universal_ble/src/models/model_exports.dart'; - - diff --git a/pubspec.yaml b/pubspec.yaml index 41e46a4..d0f4071 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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.9.1 +version: 0.9.2 homepage: https://navideck.com repository: https://github.com/Navideck/universal_ble issue_tracker: https://github.com/Navideck/universal_ble/issues