connect method
Will connect to the provided DiscoveredDevice
using its id as identifier.
This method will subscribe to connection status updates to :
- negotiate and init the GATT link with BLE device
- maintain the
_device
variable up to date (null: disconnected) - return the Future when connection is successful (ie. BLE lib gave us
DeviceConnectionState.connected
event AND we negotiated the GATT) - return a TimeoutException if connection is not established after
connectionTimeout
- add event in callbacks sinks
- return any error on the stream or any given reason for
DeviceConnectionState.disconnected
events (usually aGenericFailure
)
DooZ specific API : TODO remove before 1.0.0
The whitelist
has been introduced along with doozCustomCharacteristicUuid and is intended to be used like so :
- the whitelist is populated with MAC addresses and
shouldCheckDoozCustomService
is true - it will connect to the BLE device and check the MAC address that should be stored in the DooZ custom charac
If the read MAC is not in the
whitelist
, then this method will trigger an error event and complete with an error.
(because this is a DooZ application specific flow, we made these parameters optional)
Implementation
Future<void> connect(
final DiscoveredDevice discoveredDevice, {
Duration connectionTimeout = kDefaultConnectionTimeout,
List<String>? whitelist,
bool shouldCheckDoozCustomService = false,
}) async {
if (callbacks == null) {
throw const BleManagerException(
BleManagerFailureCode.callbacks,
'You have to set callbacks using callbacks(E callbacks) before connecting',
);
}
final _whitelist = whitelist ?? [discoveredDevice.id];
// cancel any existing sub, if connected to any device,
// events will be handled by [_deviceStatusStream] or by
// [_connectedDeviceStatusStream] first if same device as [discoveredDevice]
await _connectedDeviceStatusListener?.cancel();
final watch = Stopwatch()..start();
final _callbacks = callbacks as E;
_connectCompleter = Completer<void>();
final connectTimeout = Timer(connectionTimeout, () {
if (!_connectCompleter.isCompleted) {
_log('connect failed after ${watch.elapsedMilliseconds}ms');
_connectCompleter.completeError(TimeoutException('connection timed out', connectionTimeout));
}
_connectedDeviceStatusListener!.cancel();
});
_connectedDeviceStatusListener = _bleInstance
.connectToDevice(
id: discoveredDevice.id,
// added here so the library sets autoconnect flag to false, but timeout duration seems ignored
connectionTimeout: connectionTimeout,
)
.listen(
(ConnectionStateUpdate connectionStateUpdate) {
switch (connectionStateUpdate.connectionState) {
case DeviceConnectionState.connecting:
_device = discoveredDevice;
break;
case DeviceConnectionState.connected:
_negotiateAndInitGatt(shouldCheckDoozCustomService).then((_) async {
if (!_connectCompleter.isCompleted) {
String? deviceId = _device!.id;
if (shouldCheckDoozCustomService && !_whitelist.contains(deviceId)) {
// because white list is intended to be populated with mac addresses,
// the deviceId may not be in the list if user is on iOS as it's using generated UUIDs
// so use dooz custom BLE charac to read mac address
deviceId = await getMacId();
}
if (!_whitelist.contains(deviceId)) {
throw BleManagerException(
BleManagerFailureCode.proxyWhitelist,
'$deviceId not in $_whitelist',
);
} else {
_connectCompleter.complete();
if (!_callbacks.onDeviceReadyController.isClosed &&
_callbacks.onDeviceReadyController.hasListener) {
_callbacks.onDeviceReadyController.add(_device!);
}
}
}
}).catchError(
(e, s) {
if (!_callbacks.onErrorController.isClosed && _callbacks.onErrorController.hasListener) {
_callbacks.onErrorController.add(BleManagerCallbacksError(_device, 'GATT error', e));
}
if (!_connectCompleter.isCompleted) {
// will notify for error as the connection could not be properly established
_log('connect failed after ${watch.elapsedMilliseconds}ms');
connectTimeout.cancel();
_connectCompleter.completeError(e);
}
},
);
break;
case DeviceConnectionState.disconnecting:
// handled by _globalStatusListener
break;
case DeviceConnectionState.disconnected:
if (_device != null) {
// error may have been caught upon connection initialization or during connection
GenericFailure? maybeError = connectionStateUpdate.failure;
if (!_connectCompleter.isCompleted) {
// will notify for error as the connection could not be properly established
_log('connect failed after ${watch.elapsedMilliseconds}ms');
if (maybeError != null) {
_log('error : $maybeError');
connectTimeout.cancel();
_connectCompleter.completeError(maybeError);
} else {
const err = BleManagerException(
BleManagerFailureCode.unexpectedDisconnection,
'disconnect event before device is ready',
);
_log('error : $err');
connectTimeout.cancel();
_connectCompleter.completeError(err);
}
}
} else {
_log('seems that you were already connected to that node..'
'ignoring connection state: $connectionStateUpdate');
}
break;
}
},
cancelOnError: true,
onError: (Object error) {
if (!_callbacks.onErrorController.isClosed && _callbacks.onErrorController.hasListener) {
_callbacks.onErrorController
.add(BleManagerCallbacksError(_device, 'ERROR CAUGHT IN CONNECTION STREAM', error));
}
if (!_connectCompleter.isCompleted) {
// will notify for error as the connection could not be properly established
_log('connect failed after ${watch.elapsedMilliseconds}ms');
connectTimeout.cancel();
_connectCompleter.completeError(error);
}
});
await _connectCompleter.future;
connectTimeout.cancel();
_log('connect took ${watch.elapsedMilliseconds}ms');
}