connect method

Future<void> connect(
  1. DiscoveredDevice discoveredDevice, {
  2. Duration connectionTimeout = kDefaultConnectionTimeout,
  3. List<String>? whitelist,
  4. bool shouldCheckDoozCustomService = false,
})

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 a GenericFailure)

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');
}