Add auto-connect support (#206)
* Add autoConnect support * Enhance autoConnect functionality for Bluetooth devices - Added support for managing a set of auto-connect devices in both Android and iOS implementations. - Updated connection and disconnection logic to handle auto-reconnect scenarios appropriately. - Improved cleanup processes to prevent unwanted reconnections when devices are manually disconnected. * Refactor connection button and add auto-reconnect toggle - Updated the connection button layout for better UI consistency. - Introduced an auto-reconnect toggle to manage automatic reconnections when devices become available. - Enhanced connection and disconnection logic to respect the auto-connect setting. * Update version to 1.2.0 and add CHANGELOG for new features - Bumped version to 1.2.0. - Added CHANGELOG.md detailing new features including support for `autoConnect` and UI updates for automatic reconnection. * Improve readme * Refactor variable naming for clarity in Bluetooth connection logic - Changed variable name from `isAutoConnect` to `shouldAutoConnect` for better readability and understanding of its purpose in the connection state handling. * Refactor connection handling in UniversalBlePlugin - Simplified connection logic by always cleaning up internal state before sending connection change callbacks. - Ensured GATT resources are only closed when autoConnect is disabled, allowing for better management of Bluetooth connections. * Refactor disconnection handling in UniversalBlePlugin - Introduced a new method `handlePeripheralDisconnection` to encapsulate disconnection logic, improving code readability and maintainability. - Updated the `centralManager` methods to utilize the new disconnection handler, ensuring consistent behavior across connection state changes. * Update darwin/Classes/UniversalBlePlugin.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Foti Dim <fotios.dimanidis@a2zebra.de> Co-authored-by: Navideck Labs <130186950+navidecklabs@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
|
## 1.2.0
|
||||||
|
* Add `autoConnect` parameter to `connect()` method for automatic reconnection support on Android and iOS/macOS
|
||||||
|
|
||||||
## 1.1.0
|
## 1.1.0
|
||||||
* Add readRssi method
|
* Add readRssi method
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE
|
|||||||
| :---------------------------- | :-----: | :-: | :---: | :-----: | :---: | :-: |
|
| :---------------------------- | :-----: | :-: | :---: | :-----: | :---: | :-: |
|
||||||
| startScan/stopScan | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| startScan/stopScan | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| connect/disconnect | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| connect/disconnect | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
| autoConnect | ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ |
|
||||||
| getSystemDevices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
|
| getSystemDevices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
|
||||||
| discoverServices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| discoverServices | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| read | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| read | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
@@ -231,6 +232,13 @@ bool isConnected = await bleDevice.isConnected;
|
|||||||
BleConnectionState connectionState = await bleDevice.connectionState;
|
BleConnectionState connectionState = await bleDevice.connectionState;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Auto-connect
|
||||||
|
You can enable automatic reconnection by setting the `autoConnect` parameter to `true`. When enabled, the system will automatically attempt to reconnect to the device when it becomes available again.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
await bleDevice.connect(autoConnect: true);
|
||||||
|
```
|
||||||
|
|
||||||
### Discovering Services
|
### Discovering Services
|
||||||
|
|
||||||
After establishing a connection, services need to be discovered. This method will discover all services and their characteristics.
|
After establishing a connection, services need to be discovered. This method will discover all services and their characteristics.
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ interface UniversalBlePlatformChannel {
|
|||||||
fun startScan(filter: UniversalScanFilter?)
|
fun startScan(filter: UniversalScanFilter?)
|
||||||
fun stopScan()
|
fun stopScan()
|
||||||
fun isScanning(): Boolean
|
fun isScanning(): Boolean
|
||||||
fun connect(deviceId: String)
|
fun connect(deviceId: String, autoConnect: Boolean?)
|
||||||
fun disconnect(deviceId: String)
|
fun disconnect(deviceId: String)
|
||||||
fun setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Long, callback: (Result<Unit>) -> Unit)
|
fun setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Long, callback: (Result<Unit>) -> Unit)
|
||||||
fun discoverServices(deviceId: String, withDescriptors: Boolean, callback: (Result<List<UniversalBleService>>) -> Unit)
|
fun discoverServices(deviceId: String, withDescriptors: Boolean, callback: (Result<List<UniversalBleService>>) -> Unit)
|
||||||
@@ -686,8 +686,9 @@ interface UniversalBlePlatformChannel {
|
|||||||
channel.setMessageHandler { message, reply ->
|
channel.setMessageHandler { message, reply ->
|
||||||
val args = message as List<Any?>
|
val args = message as List<Any?>
|
||||||
val deviceIdArg = args[0] as String
|
val deviceIdArg = args[0] as String
|
||||||
|
val autoConnectArg = args[1] as Boolean?
|
||||||
val wrapped: List<Any?> = try {
|
val wrapped: List<Any?> = try {
|
||||||
api.connect(deviceIdArg)
|
api.connect(deviceIdArg, autoConnectArg)
|
||||||
listOf(null)
|
listOf(null)
|
||||||
} catch (exception: Throwable) {
|
} catch (exception: Throwable) {
|
||||||
UniversalBlePigeonUtils.wrapError(exception)
|
UniversalBlePigeonUtils.wrapError(exception)
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
private val subscriptionResultFutureList = mutableListOf<SubscriptionResultFuture>()
|
private val subscriptionResultFutureList = mutableListOf<SubscriptionResultFuture>()
|
||||||
private val pairResultFutures = mutableMapOf<String, (Result<Boolean>) -> Unit>()
|
private val pairResultFutures = mutableMapOf<String, (Result<Boolean>) -> Unit>()
|
||||||
private val rssiResultFutureList = mutableListOf<RssiResultFuture>()
|
private val rssiResultFutureList = mutableListOf<RssiResultFuture>()
|
||||||
|
private val autoConnectDevices = mutableSetOf<String>()
|
||||||
|
|
||||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
UniversalBlePlatformChannel.setUp(flutterPluginBinding.binaryMessenger, this)
|
UniversalBlePlatformChannel.setUp(flutterPluginBinding.binaryMessenger, this)
|
||||||
@@ -213,7 +214,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
return safeScanner.isScanning()
|
return safeScanner.isScanning()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun connect(deviceId: String) {
|
override fun connect(deviceId: String, autoConnect: Boolean?) {
|
||||||
// If already connected, send connected message,
|
// If already connected, send connected message,
|
||||||
// if connecting, do nothing
|
// if connecting, do nothing
|
||||||
deviceId.findGatt()?.let {
|
deviceId.findGatt()?.let {
|
||||||
@@ -232,24 +233,31 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val shouldAutoConnect = autoConnect ?: false
|
||||||
|
if (shouldAutoConnect) {
|
||||||
|
autoConnectDevices.add(deviceId)
|
||||||
|
} else {
|
||||||
|
autoConnectDevices.remove(deviceId)
|
||||||
|
}
|
||||||
val remoteDevice = bluetoothManager.adapter.getRemoteDevice(deviceId)
|
val remoteDevice = bluetoothManager.adapter.getRemoteDevice(deviceId)
|
||||||
val gatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val gatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
remoteDevice.connectGatt(
|
remoteDevice.connectGatt(
|
||||||
context,
|
context,
|
||||||
false,
|
shouldAutoConnect,
|
||||||
this,
|
this,
|
||||||
BluetoothDevice.TRANSPORT_LE
|
BluetoothDevice.TRANSPORT_LE
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
remoteDevice.connectGatt(context, false, this)
|
remoteDevice.connectGatt(context, shouldAutoConnect, this)
|
||||||
}
|
}
|
||||||
gatt.saveCacheIfNeeded()
|
gatt.saveCacheIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disconnect(deviceId: String) {
|
override fun disconnect(deviceId: String) {
|
||||||
|
autoConnectDevices.remove(deviceId)
|
||||||
val gatt = deviceId.findGatt()
|
val gatt = deviceId.findGatt()
|
||||||
if (gatt == null) {
|
if (gatt == null) {
|
||||||
|
cleanUpConnection(deviceId)
|
||||||
mainThreadHandler?.post {
|
mainThreadHandler?.post {
|
||||||
callbackChannel?.onConnectionChanged(deviceId, false, null) {}
|
callbackChannel?.onConnectionChanged(deviceId, false, null) {}
|
||||||
}
|
}
|
||||||
@@ -955,15 +963,13 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cleanConnection(gatt: BluetoothGatt) {
|
private fun cleanUpConnection(deviceId: String) {
|
||||||
gatt.removeCache()
|
|
||||||
gatt.disconnect()
|
|
||||||
val deviceDisconnectedError: FlutterError = createFlutterError(
|
val deviceDisconnectedError: FlutterError = createFlutterError(
|
||||||
UniversalBleErrorCode.DEVICE_DISCONNECTED,
|
UniversalBleErrorCode.DEVICE_DISCONNECTED,
|
||||||
"Device Disconnected",
|
"Device Disconnected",
|
||||||
)
|
)
|
||||||
readResultFutureList.removeAll {
|
readResultFutureList.removeAll {
|
||||||
if (it.deviceId == gatt.device.address) {
|
if (it.deviceId == deviceId) {
|
||||||
it.result(Result.failure(deviceDisconnectedError))
|
it.result(Result.failure(deviceDisconnectedError))
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -971,7 +977,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeResultFutureList.removeAll {
|
writeResultFutureList.removeAll {
|
||||||
if (it.deviceId == gatt.device.address) {
|
if (it.deviceId == deviceId) {
|
||||||
it.result(Result.failure(deviceDisconnectedError))
|
it.result(Result.failure(deviceDisconnectedError))
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -979,7 +985,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
subscriptionResultFutureList.removeAll {
|
subscriptionResultFutureList.removeAll {
|
||||||
if (it.deviceId == gatt.device.address) {
|
if (it.deviceId == deviceId) {
|
||||||
it.result(Result.failure(deviceDisconnectedError))
|
it.result(Result.failure(deviceDisconnectedError))
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -987,7 +993,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mtuResultFutureList.removeAll {
|
mtuResultFutureList.removeAll {
|
||||||
if (it.deviceId == gatt.device.address) {
|
if (it.deviceId == deviceId) {
|
||||||
it.result(Result.failure(deviceDisconnectedError))
|
it.result(Result.failure(deviceDisconnectedError))
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -995,7 +1001,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
discoverServicesFutureList.removeAll {
|
discoverServicesFutureList.removeAll {
|
||||||
if (it.deviceId == gatt.device.address) {
|
if (it.deviceId == deviceId) {
|
||||||
it.result(Result.failure(deviceDisconnectedError))
|
it.result(Result.failure(deviceDisconnectedError))
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -1003,7 +1009,7 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
rssiResultFutureList.removeAll {
|
rssiResultFutureList.removeAll {
|
||||||
if (it.deviceId == gatt.device.address) {
|
if (it.deviceId == deviceId) {
|
||||||
it.result(Result.failure(deviceDisconnectedError))
|
it.result(Result.failure(deviceDisconnectedError))
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -1012,6 +1018,12 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun cleanConnection(gatt: BluetoothGatt) {
|
||||||
|
gatt.removeCache()
|
||||||
|
gatt.disconnect()
|
||||||
|
cleanUpConnection(gatt.device.address)
|
||||||
|
}
|
||||||
|
|
||||||
private fun onBondStateUpdate(deviceId: String, bonded: Boolean, error: String? = null) {
|
private fun onBondStateUpdate(deviceId: String, bonded: Boolean, error: String? = null) {
|
||||||
val future = pairResultFutures.remove(deviceId)
|
val future = pairResultFutures.remove(deviceId)
|
||||||
future?.let { it(Result.success(bonded)) }
|
future?.let { it(Result.success(bonded)) }
|
||||||
@@ -1134,14 +1146,27 @@ class UniversalBlePlugin : UniversalBlePlatformChannel, BluetoothGattCallback(),
|
|||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
|
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
|
||||||
cleanConnection(gatt)
|
val deviceId = gatt.device.address
|
||||||
|
val shouldAutoConnect = autoConnectDevices.contains(deviceId)
|
||||||
|
|
||||||
|
// Always clean up internal state (futures, etc.)
|
||||||
|
cleanUpConnection(deviceId)
|
||||||
|
|
||||||
|
// Send connection changed callback
|
||||||
mainThreadHandler?.post {
|
mainThreadHandler?.post {
|
||||||
callbackChannel?.onConnectionChanged(
|
callbackChannel?.onConnectionChanged(
|
||||||
gatt.device.address, false, status.parseHciErrorCode()
|
deviceId, false, status.parseHciErrorCode()
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
UniversalBleLogger.logDebug("Closing gatt for ${gatt.device.name}")
|
|
||||||
gatt.close()
|
if (!shouldAutoConnect) {
|
||||||
|
// Only close GATT resources when autoConnect is disabled
|
||||||
|
gatt.removeCache()
|
||||||
|
gatt.disconnect()
|
||||||
|
UniversalBleLogger.logDebug("Closing gatt for ${gatt.device.name}")
|
||||||
|
gatt.close()
|
||||||
|
}
|
||||||
|
// When autoConnect is enabled, keep GATT open for Android to reconnect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ protocol UniversalBlePlatformChannel {
|
|||||||
func startScan(filter: UniversalScanFilter?) throws
|
func startScan(filter: UniversalScanFilter?) throws
|
||||||
func stopScan() throws
|
func stopScan() throws
|
||||||
func isScanning() throws -> Bool
|
func isScanning() throws -> Bool
|
||||||
func connect(deviceId: String) throws
|
func connect(deviceId: String, autoConnect: Bool?) throws
|
||||||
func disconnect(deviceId: String) throws
|
func disconnect(deviceId: String) throws
|
||||||
func setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void)
|
func setNotifiable(deviceId: String, service: String, characteristic: String, bleInputProperty: Int64, completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
func discoverServices(deviceId: String, withDescriptors: Bool, completion: @escaping (Result<[UniversalBleService], Error>) -> Void)
|
func discoverServices(deviceId: String, withDescriptors: Bool, completion: @escaping (Result<[UniversalBleService], Error>) -> Void)
|
||||||
@@ -681,8 +681,9 @@ class UniversalBlePlatformChannelSetup {
|
|||||||
connectChannel.setMessageHandler { message, reply in
|
connectChannel.setMessageHandler { message, reply in
|
||||||
let args = message as! [Any?]
|
let args = message as! [Any?]
|
||||||
let deviceIdArg = args[0] as! String
|
let deviceIdArg = args[0] as! String
|
||||||
|
let autoConnectArg: Bool? = nilOrValue(args[1])
|
||||||
do {
|
do {
|
||||||
try api.connect(deviceId: deviceIdArg)
|
try api.connect(deviceId: deviceIdArg, autoConnect: autoConnectArg)
|
||||||
reply(wrapResult(nil))
|
reply(wrapResult(nil))
|
||||||
} catch {
|
} catch {
|
||||||
reply(wrapError(error))
|
reply(wrapError(error))
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
|
|||||||
private var discoverServicesFutures = [DiscoverServicesFuture]()
|
private var discoverServicesFutures = [DiscoverServicesFuture]()
|
||||||
private var rssiReadFutures = [RssiReadFuture]()
|
private var rssiReadFutures = [RssiReadFuture]()
|
||||||
private var isManageScanning = false
|
private var isManageScanning = false
|
||||||
|
private var autoConnectDevices = Set<String>()
|
||||||
|
|
||||||
init(callbackChannel: UniversalBleCallbackChannel) {
|
init(callbackChannel: UniversalBleCallbackChannel) {
|
||||||
self.callbackChannel = callbackChannel
|
self.callbackChannel = callbackChannel
|
||||||
@@ -129,15 +130,41 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
|
|||||||
UniversalBleLogger.shared.setLogLevel(logLevel)
|
UniversalBleLogger.shared.setLogLevel(logLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func connect(deviceId: String) throws {
|
func connect(deviceId: String, autoConnect: Bool?) throws {
|
||||||
let peripheral = try deviceId.getPeripheral(manager: manager)
|
let peripheral = try deviceId.getPeripheral(manager: manager)
|
||||||
peripheral.delegate = self
|
peripheral.delegate = self
|
||||||
manager.connect(peripheral)
|
let shouldAutoConnect = autoConnect ?? false
|
||||||
|
|
||||||
|
if shouldAutoConnect {
|
||||||
|
autoConnectDevices.insert(deviceId)
|
||||||
|
if #available(iOS 17.0, macOS 14.0, watchOS 10.0, tvOS 17.0, *) {
|
||||||
|
let options: [String: Any] = [CBConnectPeripheralOptionEnableAutoReconnect: true]
|
||||||
|
manager.connect(peripheral, options: options)
|
||||||
|
} else {
|
||||||
|
// Auto-reconnect via CBConnectPeripheralOptionEnableAutoReconnect is only
|
||||||
|
// available on iOS 17.0 / macOS 14.0 / watchOS 10.0 / tvOS 17.0 and later.
|
||||||
|
// On earlier OS versions, enabling `autoConnect` will NOT provide automatic
|
||||||
|
// reconnection behavior. Any desired reconnection must be handled manually
|
||||||
|
// (e.g., in central manager delegate callbacks).
|
||||||
|
UniversalBleLogger.shared.logInfo(
|
||||||
|
"autoConnect requested for device \(deviceId), " +
|
||||||
|
"but automatic reconnection via CBConnectPeripheralOptionEnableAutoReconnect " +
|
||||||
|
"is only available on iOS 17+/macOS 14+/watchOS 10+/tvOS 17+. " +
|
||||||
|
"On this OS version, reconnections must be handled manually."
|
||||||
|
)
|
||||||
|
manager.connect(peripheral)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
autoConnectDevices.remove(deviceId)
|
||||||
|
manager.connect(peripheral)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func disconnect(deviceId: String) throws {
|
func disconnect(deviceId: String) throws {
|
||||||
|
autoConnectDevices.remove(deviceId)
|
||||||
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
|
guard let peripheral = deviceId.findPeripheral(manager: manager) else {
|
||||||
callbackChannel.onConnectionChanged(deviceId: deviceId, connected: false, error: nil) { _ in }
|
callbackChannel.onConnectionChanged(deviceId: deviceId, connected: false, error: nil) { _ in }
|
||||||
|
cleanUpConnection(deviceId: deviceId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if peripheral.state != CBPeripheralState.disconnected {
|
if peripheral.state != CBPeripheralState.disconnected {
|
||||||
@@ -445,9 +472,33 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
|
|||||||
callbackChannel.onConnectionChanged(deviceId: peripheral.uuid.uuidString, connected: true, error: nil) { _ in }
|
callbackChannel.onConnectionChanged(deviceId: peripheral.uuid.uuidString, connected: true, error: nil) { _ in }
|
||||||
}
|
}
|
||||||
|
|
||||||
public func centralManager(_: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error _: Error?) {
|
private func handlePeripheralDisconnection(deviceId: String, error: Error?) {
|
||||||
callbackChannel.onConnectionChanged(deviceId: peripheral.uuid.uuidString, connected: false, error: nil) { _ in }
|
autoConnectDevices.remove(deviceId)
|
||||||
cleanUpConnection(deviceId: peripheral.uuid.uuidString)
|
callbackChannel.onConnectionChanged(deviceId: deviceId, connected: false, error: error?.localizedDescription) { _ in }
|
||||||
|
cleanUpConnection(deviceId: deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func centralManager(
|
||||||
|
_: CBCentralManager,
|
||||||
|
didDisconnectPeripheral peripheral: CBPeripheral,
|
||||||
|
timestamp: CFAbsoluteTime,
|
||||||
|
isReconnecting: Bool,
|
||||||
|
error: Error?
|
||||||
|
) {
|
||||||
|
let deviceId = peripheral.uuid.uuidString
|
||||||
|
|
||||||
|
if #available(iOS 17.0, macOS 14.0, watchOS 10.0, tvOS 17.0, *) {
|
||||||
|
if isReconnecting {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePeripheralDisconnection(deviceId: deviceId, error: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func centralManager(_: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
||||||
|
let deviceId = peripheral.uuid.uuidString
|
||||||
|
handlePeripheralDisconnection(deviceId: deviceId, error: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func centralManager(_: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
public func centralManager(_: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## 1.1.0
|
||||||
|
* Add support for `autoConnect` parameter
|
||||||
|
* Display RSSI values in device details
|
||||||
|
* Persist filters
|
||||||
|
* Fix clear log button
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
* Initial release
|
||||||
@@ -52,7 +52,7 @@ class MockUniversalBle extends UniversalBlePlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> connect(String deviceId, {Duration? connectionTimeout}) async {
|
Future<void> connect(String deviceId, {bool autoConnect = false, Duration? connectionTimeout}) async {
|
||||||
updateConnection(deviceId, true);
|
updateConnection(deviceId, true);
|
||||||
_connectionStateMap[deviceId] = BleConnectionState.connected;
|
_connectionStateMap[deviceId] = BleConnectionState.connected;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class _AppDrawerState extends State<AppDrawer> {
|
|||||||
applicationName: 'Universal BLE',
|
applicationName: 'Universal BLE',
|
||||||
applicationVersion:
|
applicationVersion:
|
||||||
"${snapshot.data?.version} (${snapshot.data?.buildNumber})",
|
"${snapshot.data?.version} (${snapshot.data?.buildNumber})",
|
||||||
applicationLegalese: '\u{a9} 2025 Navideck',
|
applicationLegalese: '\u{a9} 2023 Navideck',
|
||||||
aboutBoxChildren: [
|
aboutBoxChildren: [
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
RichText(
|
RichText(
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
|||||||
bool _isDeviceInfoExpanded = false;
|
bool _isDeviceInfoExpanded = false;
|
||||||
bool _isDeviceActionsExpanded = true;
|
bool _isDeviceActionsExpanded = true;
|
||||||
final Map<String, bool> _subscribedCharacteristics = {};
|
final Map<String, bool> _subscribedCharacteristics = {};
|
||||||
|
bool _autoConnect = false;
|
||||||
|
|
||||||
StreamSubscription? connectionStreamSubscription;
|
StreamSubscription? connectionStreamSubscription;
|
||||||
StreamSubscription? pairingStateSubscription;
|
StreamSubscription? pairingStateSubscription;
|
||||||
@@ -688,59 +689,155 @@ class _PeripheralDetailPageState extends State<PeripheralDetailPage> {
|
|||||||
|
|
||||||
Widget _buildConnectDisconnectButton() {
|
Widget _buildConnectDisconnectButton() {
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
return SizedBox(
|
return Padding(
|
||||||
width: double.infinity,
|
padding: const EdgeInsets.symmetric(
|
||||||
child: Padding(
|
horizontal: 16.0,
|
||||||
padding: const EdgeInsets.symmetric(
|
vertical: 8.0,
|
||||||
horizontal: 16.0,
|
),
|
||||||
vertical: 8.0,
|
child: Column(
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: ElevatedButton.icon(
|
children: [
|
||||||
onPressed: () async {
|
// AutoConnect toggle
|
||||||
if (isConnected) {
|
Card(
|
||||||
await _executeWithLoading(
|
elevation: 1,
|
||||||
() async {
|
|
||||||
await bleDevice.disconnect();
|
|
||||||
_addLog("DisconnectResult", true);
|
|
||||||
},
|
|
||||||
onError: (error) {
|
|
||||||
_addLog('DisconnectError', error);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await _executeWithLoading(
|
|
||||||
() async {
|
|
||||||
await bleDevice.connect();
|
|
||||||
_addLog("ConnectionResult", true);
|
|
||||||
},
|
|
||||||
onError: (error) {
|
|
||||||
_addLog(
|
|
||||||
'ConnectError (${error.runtimeType})',
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
isConnected ? Icons.bluetooth_disabled : Icons.bluetooth_connected,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
label: Text(isConnected ? 'Disconnect' : 'Connect'),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor:
|
|
||||||
isConnected ? colorScheme.error : colorScheme.primary,
|
|
||||||
foregroundColor:
|
|
||||||
isConnected ? colorScheme.onError : colorScheme.onPrimary,
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
elevation: 2,
|
color: colorScheme.surfaceContainerHighest,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.autorenew,
|
||||||
|
size: 18,
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Auto Reconnect',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Automatically reconnect when device becomes available',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: colorScheme.onSurface.withValues(alpha: 0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: _autoConnect,
|
||||||
|
onChanged: (value) async {
|
||||||
|
// If toggling off auto-connect, disconnect to prevent unwanted reconnections
|
||||||
|
if (!value && _autoConnect) {
|
||||||
|
if (isConnected) {
|
||||||
|
// Device is connected, disconnect it to prevent auto-reconnect
|
||||||
|
await _executeWithLoading(
|
||||||
|
() async {
|
||||||
|
await bleDevice.disconnect();
|
||||||
|
_addLog("DisconnectResult",
|
||||||
|
"Disconnected to disable auto-reconnect");
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
_addLog('DisconnectError', error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Device is already disconnected, but call disconnect() anyway
|
||||||
|
// to ensure cleanup and prevent any pending auto-reconnection attempts
|
||||||
|
await _executeWithLoading(
|
||||||
|
() async {
|
||||||
|
await bleDevice.disconnect();
|
||||||
|
_addLog("DisconnectResult",
|
||||||
|
"Cleanup performed to prevent auto-reconnect");
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
_addLog('DisconnectError', error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_autoConnect = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 12),
|
||||||
|
// Connect/Disconnect button
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () async {
|
||||||
|
if (isConnected) {
|
||||||
|
await _executeWithLoading(
|
||||||
|
() async {
|
||||||
|
await bleDevice.disconnect();
|
||||||
|
_addLog("DisconnectResult", true);
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
_addLog('DisconnectError', error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await _executeWithLoading(
|
||||||
|
() async {
|
||||||
|
await bleDevice.connect(autoConnect: _autoConnect);
|
||||||
|
_addLog(
|
||||||
|
"ConnectionResult",
|
||||||
|
"Connected${_autoConnect ? ' (Auto-reconnect enabled)' : ''}",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
_addLog(
|
||||||
|
'ConnectError (${error.runtimeType})',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
isConnected
|
||||||
|
? Icons.bluetooth_disabled
|
||||||
|
: Icons.bluetooth_connected,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
label: Text(isConnected ? 'Disconnect' : 'Connect'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor:
|
||||||
|
isConnected ? colorScheme.error : colorScheme.primary,
|
||||||
|
foregroundColor:
|
||||||
|
isConnected ? colorScheme.onError : colorScheme.onPrimary,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import 'package:universal_ble_example/home/scanner_screen.dart';
|
|||||||
Future<bool> initializeApp() async {
|
Future<bool> initializeApp() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await StorageService.instance.init();
|
await StorageService.instance.init();
|
||||||
// await UniversalBle.setLogLevel(BleLogLevel.verbose);
|
await UniversalBle.setLogLevel(BleLogLevel.verbose);
|
||||||
return await UniversalBle.hasPermissions(
|
return await UniversalBle.hasPermissions(
|
||||||
withAndroidFineLocation: false,
|
withAndroidFineLocation: false,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ PRODUCT_NAME = Universal BLE
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalble
|
PRODUCT_BUNDLE_IDENTIFIER = com.navideck.universalble
|
||||||
|
|
||||||
// The copyright displayed in application information
|
// The copyright displayed in application information
|
||||||
PRODUCT_COPYRIGHT = Copyright © 2025 com.navideck. All rights reserved.
|
PRODUCT_COPYRIGHT = Copyright © 2023 com.navideck. All rights reserved.
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ package_rename_config:
|
|||||||
macos:
|
macos:
|
||||||
app_name: "Universal BLE"
|
app_name: "Universal BLE"
|
||||||
package_name: "com.navideck.universalble"
|
package_name: "com.navideck.universalble"
|
||||||
copyright_notice: "Copyright © 2025 com.navideck. All rights reserved."
|
copyright_notice: "Copyright © 2023 com.navideck. All rights reserved."
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
app_name: "Universal BLE"
|
app_name: "Universal BLE"
|
||||||
organization: "Navideck"
|
organization: "Navideck"
|
||||||
copyright_notice: "Copyright © 2025 com.navideck. All rights reserved."
|
copyright_notice: "Copyright © 2023 com.navideck. All rights reserved."
|
||||||
exe_name: "universal_ble"
|
exe_name: "universal_ble"
|
||||||
|
|
||||||
web:
|
web:
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ BEGIN
|
|||||||
VALUE "FileDescription", "Universal BLE" "\0"
|
VALUE "FileDescription", "Universal BLE" "\0"
|
||||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||||
VALUE "InternalName", "Universal BLE" "\0"
|
VALUE "InternalName", "Universal BLE" "\0"
|
||||||
VALUE "LegalCopyright", "Copyright © 2025 com.navideck. All rights reserved." "\0"
|
VALUE "LegalCopyright", "Copyright © 2023 com.navideck. All rights reserved." "\0"
|
||||||
VALUE "OriginalFilename", "universal_ble.exe" "\0"
|
VALUE "OriginalFilename", "universal_ble.exe" "\0"
|
||||||
VALUE "ProductName", "Universal BLE" "\0"
|
VALUE "ProductName", "Universal BLE" "\0"
|
||||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ extension BleDeviceExtension on BleDevice {
|
|||||||
BleConnectionState.connected;
|
BleConnectionState.connected;
|
||||||
|
|
||||||
/// Connects to the device.
|
/// Connects to the device.
|
||||||
Future<void> connect() => UniversalBle.connect(deviceId);
|
/// [autoConnect] enables automatic reconnection when the device becomes available.
|
||||||
|
Future<void> connect({bool autoConnect = false, Duration? timeout}) =>
|
||||||
|
UniversalBle.connect(deviceId, autoConnect: autoConnect, timeout: timeout);
|
||||||
|
|
||||||
/// Disconnects from the device.
|
/// Disconnects from the device.
|
||||||
Future<void> disconnect() => UniversalBle.disconnect(deviceId);
|
Future<void> disconnect() => UniversalBle.disconnect(deviceId);
|
||||||
|
|||||||
+22
-10
@@ -134,16 +134,26 @@ class UniversalBle {
|
|||||||
/// It is advised to stop scanning before connecting.
|
/// It is advised to stop scanning before connecting.
|
||||||
/// It throws error if device connection fails.
|
/// It throws error if device connection fails.
|
||||||
/// Default connection timeout is 60 sec.
|
/// Default connection timeout is 60 sec.
|
||||||
|
///
|
||||||
|
/// [autoConnect] enables automatic reconnection when the device becomes available.
|
||||||
|
/// Default value is `false`.
|
||||||
|
/// Ignored on `Windows`, `Linux` and `Web`.
|
||||||
|
///
|
||||||
|
/// Call [disconnect] to prevent auto-reconnect even while a device is disconnected.
|
||||||
|
///
|
||||||
/// Can throw `ConnectionException` or `PlatformException`.
|
/// Can throw `ConnectionException` or `PlatformException`.
|
||||||
static Future<void> connect(
|
static Future<void> connect(
|
||||||
String deviceId, {
|
String deviceId, {
|
||||||
Duration? timeout,
|
Duration? timeout,
|
||||||
|
bool autoConnect = false,
|
||||||
}) async {
|
}) async {
|
||||||
timeout ??= const Duration(seconds: 60);
|
timeout ??= const Duration(seconds: 60);
|
||||||
Completer<bool> completer =
|
Completer<bool> completer =
|
||||||
_connectionEventCompleter(deviceId, timeout: timeout);
|
_connectionEventCompleter(deviceId, timeout: timeout);
|
||||||
|
|
||||||
_platform.connect(deviceId, connectionTimeout: timeout).catchError(
|
_platform
|
||||||
|
.connect(deviceId, connectionTimeout: timeout, autoConnect: autoConnect)
|
||||||
|
.catchError(
|
||||||
(error) {
|
(error) {
|
||||||
if (completer.isCompleted) return;
|
if (completer.isCompleted) return;
|
||||||
completer.completeError(ConnectionException(error));
|
completer.completeError(ConnectionException(error));
|
||||||
@@ -169,15 +179,6 @@ class UniversalBle {
|
|||||||
UniversalLogger.logError("Get connection state failed: $e");
|
UniversalLogger.logError("Get connection state failed: $e");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionState == BleConnectionState.disconnected ||
|
|
||||||
connectionState == BleConnectionState.disconnecting) {
|
|
||||||
_platform.updateConnection(deviceId, false);
|
|
||||||
UniversalLogger.logInfo(
|
|
||||||
"Device $deviceId already disconnected: $connectionState",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Completer<bool> completer =
|
Completer<bool> completer =
|
||||||
_connectionEventCompleter(deviceId, timeout: timeout);
|
_connectionEventCompleter(deviceId, timeout: timeout);
|
||||||
@@ -192,6 +193,17 @@ class UniversalBle {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (connectionState == BleConnectionState.disconnected ||
|
||||||
|
connectionState == BleConnectionState.disconnecting) {
|
||||||
|
// Device was already disconnected, but we still called platform disconnect
|
||||||
|
// to prevent auto-reconnect. Update connection state and return.
|
||||||
|
_platform.updateConnection(deviceId, false);
|
||||||
|
UniversalLogger.logInfo(
|
||||||
|
"Device $deviceId already disconnected: $connectionState. Cleanup performed to prevent auto-reconnect.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (await completer.future.timeout(timeout)) {
|
if (await completer.future.timeout(timeout)) {
|
||||||
UniversalLogger.logError(
|
UniversalLogger.logError(
|
||||||
"Device $deviceId is still connected after disconnect attempt",
|
"Device $deviceId is still connected after disconnect attempt",
|
||||||
|
|||||||
@@ -141,7 +141,8 @@ class UniversalBleLinux extends UniversalBlePlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> connect(String deviceId, {Duration? connectionTimeout}) async {
|
Future<void> connect(String deviceId, {Duration? connectionTimeout, bool autoConnect = false}) async {
|
||||||
|
// Note: autoConnect is not directly supported on Linux platform
|
||||||
final device = _findDeviceById(deviceId);
|
final device = _findDeviceById(deviceId);
|
||||||
if (device.connected) {
|
if (device.connected) {
|
||||||
updateConnection(deviceId, true);
|
updateConnection(deviceId, true);
|
||||||
|
|||||||
@@ -776,7 +776,7 @@ class UniversalBlePlatformChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> connect(String deviceId) async {
|
Future<void> connect(String deviceId, {bool? autoConnect}) async {
|
||||||
final pigeonVar_channelName =
|
final pigeonVar_channelName =
|
||||||
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect$pigeonVar_messageChannelSuffix';
|
'dev.flutter.pigeon.universal_ble.UniversalBlePlatformChannel.connect$pigeonVar_messageChannelSuffix';
|
||||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
@@ -785,7 +785,7 @@ class UniversalBlePlatformChannel {
|
|||||||
binaryMessenger: pigeonVar_binaryMessenger,
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
);
|
);
|
||||||
final Future<Object?> pigeonVar_sendFuture =
|
final Future<Object?> pigeonVar_sendFuture =
|
||||||
pigeonVar_channel.send(<Object?>[deviceId]);
|
pigeonVar_channel.send(<Object?>[deviceId, autoConnect]);
|
||||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
if (pigeonVar_replyList == null) {
|
if (pigeonVar_replyList == null) {
|
||||||
throw _createConnectionError(pigeonVar_channelName);
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> connect(String deviceId, {Duration? connectionTimeout}) =>
|
Future<void> connect(String deviceId, {Duration? connectionTimeout, bool autoConnect = false}) =>
|
||||||
_executeWithErrorHandling(() => _channel.connect(deviceId));
|
_executeWithErrorHandling(() => _channel.connect(deviceId, autoConnect: autoConnect));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> disconnect(String deviceId) =>
|
Future<void> disconnect(String deviceId) =>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ abstract class UniversalBlePlatform {
|
|||||||
|
|
||||||
Future<bool> isScanning();
|
Future<bool> isScanning();
|
||||||
|
|
||||||
Future<void> connect(String deviceId, {Duration? connectionTimeout});
|
Future<void> connect(String deviceId, {Duration? connectionTimeout, bool autoConnect = false});
|
||||||
|
|
||||||
Future<void> disconnect(String deviceId);
|
Future<void> disconnect(String deviceId);
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ class UniversalBleWeb extends UniversalBlePlatform {
|
|||||||
Future<void> connect(
|
Future<void> connect(
|
||||||
String deviceId, {
|
String deviceId, {
|
||||||
Duration? connectionTimeout = const Duration(seconds: 10),
|
Duration? connectionTimeout = const Duration(seconds: 10),
|
||||||
|
bool autoConnect = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
// Note: autoConnect is not directly supported on Web platform
|
||||||
var device = _getDeviceById(deviceId);
|
var device = _getDeviceById(deviceId);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
throw UniversalBleException(
|
throw UniversalBleException(
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ abstract class UniversalBlePlatformChannel {
|
|||||||
|
|
||||||
bool isScanning();
|
bool isScanning();
|
||||||
|
|
||||||
void connect(String deviceId);
|
void connect(String deviceId, {bool? autoConnect});
|
||||||
|
|
||||||
void disconnect(String deviceId);
|
void disconnect(String deviceId);
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
name: universal_ble
|
name: universal_ble
|
||||||
description: A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter
|
description: A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
homepage: https://navideck.com
|
homepage: https://navideck.com
|
||||||
repository: https://github.com/Navideck/universal_ble
|
repository: https://github.com/Navideck/universal_ble
|
||||||
issue_tracker: https://github.com/Navideck/universal_ble/issues
|
issue_tracker: https://github.com/Navideck/universal_ble/issues
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import 'package:universal_ble/universal_ble.dart';
|
|||||||
|
|
||||||
abstract class UniversalBlePlatformMock extends UniversalBlePlatform {
|
abstract class UniversalBlePlatformMock extends UniversalBlePlatform {
|
||||||
@override
|
@override
|
||||||
Future<void> connect(String deviceId, {Duration? connectionTimeout}) {
|
Future<void> connect(String deviceId,
|
||||||
|
{bool autoConnect = false, Duration? connectionTimeout}) {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -791,7 +791,9 @@ void UniversalBlePlatformChannel::SetUp(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto& device_id_arg = std::get<std::string>(encodable_device_id_arg);
|
const auto& device_id_arg = std::get<std::string>(encodable_device_id_arg);
|
||||||
std::optional<FlutterError> output = api->Connect(device_id_arg);
|
const auto& encodable_auto_connect_arg = args.at(1);
|
||||||
|
const auto* auto_connect_arg = std::get_if<bool>(&encodable_auto_connect_arg);
|
||||||
|
std::optional<FlutterError> output = api->Connect(device_id_arg, auto_connect_arg);
|
||||||
if (output.has_value()) {
|
if (output.has_value()) {
|
||||||
reply(WrapError(output.value()));
|
reply(WrapError(output.value()));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -396,7 +396,9 @@ class UniversalBlePlatformChannel {
|
|||||||
virtual std::optional<FlutterError> StartScan(const UniversalScanFilter* filter) = 0;
|
virtual std::optional<FlutterError> StartScan(const UniversalScanFilter* filter) = 0;
|
||||||
virtual std::optional<FlutterError> StopScan() = 0;
|
virtual std::optional<FlutterError> StopScan() = 0;
|
||||||
virtual ErrorOr<bool> IsScanning() = 0;
|
virtual ErrorOr<bool> IsScanning() = 0;
|
||||||
virtual std::optional<FlutterError> Connect(const std::string& device_id) = 0;
|
virtual std::optional<FlutterError> Connect(
|
||||||
|
const std::string& device_id,
|
||||||
|
const bool* auto_connect) = 0;
|
||||||
virtual std::optional<FlutterError> Disconnect(const std::string& device_id) = 0;
|
virtual std::optional<FlutterError> Disconnect(const std::string& device_id) = 0;
|
||||||
virtual void SetNotifiable(
|
virtual void SetNotifiable(
|
||||||
const std::string& device_id,
|
const std::string& device_id,
|
||||||
|
|||||||
@@ -261,7 +261,8 @@ UniversalBlePlugin::SetLogLevel(const UniversalBleLogLevel &log_level) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<FlutterError>
|
std::optional<FlutterError>
|
||||||
UniversalBlePlugin::Connect(const std::string &device_id) {
|
UniversalBlePlugin::Connect(const std::string &device_id, const bool *auto_connect) {
|
||||||
|
// Note: autoConnect is not directly supported on Windows platform
|
||||||
ConnectAsync(str_to_mac_address(device_id));
|
ConnectAsync(str_to_mac_address(device_id));
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ private:
|
|||||||
StartScan(const UniversalScanFilter *filter) override;
|
StartScan(const UniversalScanFilter *filter) override;
|
||||||
std::optional<FlutterError> StopScan() override;
|
std::optional<FlutterError> StopScan() override;
|
||||||
ErrorOr<bool> IsScanning() override;
|
ErrorOr<bool> IsScanning() override;
|
||||||
std::optional<FlutterError> Connect(const std::string &device_id) override;
|
std::optional<FlutterError> Connect(const std::string &device_id, const bool *auto_connect) override;
|
||||||
std::optional<FlutterError> Disconnect(const std::string &device_id) override;
|
std::optional<FlutterError> Disconnect(const std::string &device_id) override;
|
||||||
ErrorOr<bool> HasPermissions(bool with_android_fine_location) override;
|
ErrorOr<bool> HasPermissions(bool with_android_fine_location) override;
|
||||||
void RequestPermissions(
|
void RequestPermissions(
|
||||||
|
|||||||
Reference in New Issue
Block a user