bluetooth_le_handler.dart 6.94 KB
Newer Older
1 2 3
import 'dart:async';
import 'dart:io';

4
import 'package:flutter/services.dart';
5 6
import 'package:flutter_blue/flutter_blue.dart';
import 'package:kotlin_flavor/scope_functions.dart';
7
import 'package:logair_application/logic/handlers/bluetooth_wake_handler.dart';
8 9 10
import 'package:logair_application/logic/handlers/preference_handler.dart';
import 'package:logair_application/utils/enums/bluetooth_connection_status.dart';
import 'package:logair_application/logic/handlers/data_handler.dart';
11
import 'package:logair_application/utils/enums/preference_keys.dart';
12

Nicolas Richard Walter Boeckh's avatar
Tweaks  
Nicolas Richard Walter Boeckh committed
13 14
// TODO Attempt reacquiring device after 5 seconds inactivity.

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
/// Serves as a wrapper class around [FlutterBlue], and helps integrate it into the application with extensions
class BTLEHandler {
  /// [Singleton]() instantiation factory.
  factory BTLEHandler() => _singleton;
  
  /// Internal static [Singleton]() constructor.
  BTLEHandler._internal() {
    this._fblueInstance = FlutterBlue.instance;
    //this._fblueInstance.setLogLevel(LogLevel.);
  }

  /// Internal static [Singleton]() reference.
  static final BTLEHandler _singleton = new BTLEHandler._internal();
  
  /// The referenced [BluetoothCharacteristic].
  /// Should be singular, unless we plan to broadcast over channels.
  BluetoothCharacteristic _characteristic;

  /// The static [FlutterBlue] instance that this handler should reference.
  /// Enable avoiding extraneous GC, and referencing errors.
  FlutterBlue _fblueInstance;

  /// @Getter for [_fblueInstance]
  FlutterBlue get fblueInstance => this._fblueInstance;

  /// [BluetoothConnectionStatus] that will serve as the baseline to display status to the user to.
  BluetoothConnectionStatus _bluetoothConnectionStatus = BluetoothConnectionStatus.BTSTATUS_NOT_STREAMING;

  /// @Getter to get this [BluetoothDevice]s MAC address.
  String get address => this._device?.let((it) => it.id.toString());

  /// The [BluetoothDevice] that this handler references.
  BluetoothDevice _device;

  /// Method that sets the [BluetoothDevice] to be listened to by [FlutterBlue].
  void setDevice(BluetoothDevice device) => device?.let((it) => this._device = it);  

  /// [Stream] that notifes when the app is connected to the [BluetoothDevice].
  Stream<bool> isDeviceConnected() async* {
    while (true) {
      yield (this._device != null);
      await Future.delayed(Duration(milliseconds: 750));
    }
  }

  /// [Stream] that notifes the [BluetoothConnectionStatus] every 2 seconds.
  Stream<BluetoothConnectionStatus> getConnectionStatus() async* {
    while (true) {
      yield _bluetoothConnectionStatus;
Nicolas Richard Walter Boeckh's avatar
Tweaks  
Nicolas Richard Walter Boeckh committed
64
      await Future.delayed(Duration(milliseconds: 250));
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
    }
  }

  /// Makes the phone connect to the [BluetoothDevice] and initiate [BluetoothService] discovery.
  Future<void> connect() async {
    /// If the [BluetoothDevice] is connected to this handler.
    if (this._device != null)
      await this._asyncConnectionProcess();
  }

  /// Makes the smartphone disconnect from the [BluetoothDevice].
  Future<dynamic> disconnect() => this._device?.let((it) async {
    /// Remove notifications and references to the [BluetoothDevice] or [BluetoothCharacteristic]s.
    if (_characteristic != null)
      await _characteristic.setNotifyValue(false);
    _characteristic = null;
    _device = null;
    _bluetoothConnectionStatus = BluetoothConnectionStatus.BTSTATUS_NOT_STREAMING;
83
    DataHandler().dispose();
84
    BTWakeHandler().switchState(start: false);
85 86 87 88 89 90 91 92 93 94 95 96
    return it.disconnect();
  });

  /// Wrapper function that tries to make the handler connect to the desired [BluetoothCharacteristic], if it exists.
  Future<bool> _setupCharacteristics() async {
    List<BluetoothService> services = [];
    List<BluetoothCharacteristic> characteristics = [];

    /// Termination status variable.
    bool success = false;

    if (this._device != null) {
97 98 99 100 101
      final String bluetoothLeCc254xServiceUUID = await PreferencesHandler().getPreferencesString(PreferenceKeys.BT__SERVICE_UUID.key);
      final String bluetoothLeCc254xReadUUID = await PreferencesHandler().getPreferencesString(PreferenceKeys.BT__CHARACTERISTIC_UUID.key);

      print('$bluetoothLeCc254xServiceUUID\n$bluetoothLeCc254xReadUUID');

102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
      /// Get the first round of [BluetoothService]s and apply filter to desired Service UUID.
      services = await this._device.services.first;
      services = services.where((s) => s.uuid.toString() == bluetoothLeCc254xServiceUUID).toList();
      
      /// If the [BluetoothService] exists.
      if (services.length > 0) {
        /// Get the associated [BluetoothCharacteristic]s and apply filter to desired Characteristic UUID.
        characteristics = services[0].characteristics;
        characteristics = characteristics.where((c) => c.uuid.toString() == bluetoothLeCc254xReadUUID).toList();

        /// If the [BluetoothCharacteristic] exists.
        if (characteristics.length > 0) {
          /// Set the [BluetoothCharacteristic] to notify new data.
          if (!characteristics[0].isNotifying)
            await characteristics[0].setNotifyValue(true);
          _characteristic = characteristics[0];
          success = true;
        }
      }
    }

    return success;
  }

  /// Wrapper function that enable data handoff to [DataHandler];
  Future<bool> _setupListener() async {
    bool success = false;

    /// Guard against unwanted trigger.
    if (_characteristic != null) {
      _bluetoothConnectionStatus = BluetoothConnectionStatus.BTSTATUS_STREAMING;

      /// Apply a pipe transform to the values that are notified by the [BluetoothCharacteristic].
      _characteristic.value.transform(
        new StreamTransformer.fromHandlers(
          handleData: (List<int> list, EventSink<List<int>> sink) => 
            sink.add((list.isNotEmpty) ? list : []))
      ).listen((data) => 
        /// Handoff the data to the [DataHandler].
        DataHandler().addData(data)
      );
      print("RETURNS");
      success = true;
    } else {
      _bluetoothConnectionStatus = BluetoothConnectionStatus.BTSTATUS_NOT_STREAMING;
    }
    return success;
  }

  /// Wrapper function that aggregates all of the steps to connect to a [BluetoothDevice] and stream its data.
  Future<void> _asyncConnectionProcess() async {
153 154 155 156 157 158
    try {
      await this._device.connect();
    } on PlatformException {
      await this._device.disconnect();
      await this._device.connect();
    }
159 160 161 162 163 164 165 166 167 168 169
    if (Platform.isAndroid) {
      /// Android can negotiate greater Message Transmission Units.
      /// 517 is the maximum defined in the BLE specification, the request will assign the largest available MTU instead.
      _bluetoothConnectionStatus = BluetoothConnectionStatus.BTSTATUS_SETTING_MTU;
      await this._device.requestMtu(517);
    }
    /// @bug{Paul Demarco has been notified} Delay to let MTU request sink in, otherwise hindering [BluetoothService] discovery.
    await Future.delayed(Duration(seconds: 1));
    await this._device.discoverServices();
    await _setupCharacteristics();
    await _setupListener();
170
    BTWakeHandler().switchState(start: true);
171 172
  }
}