-
Notifications
You must be signed in to change notification settings - Fork 3
Bluetooth #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bluetooth #25
Changes from all commits
5a37c04
65cf38d
0c53add
8697b35
38e939e
15e4ab8
ff2854f
428d554
118ed85
baebeab
0c23975
37b8cc2
a756532
6c66e52
c7929d5
6663be0
0809597
437ff84
dd515c8
8a56384
475aef0
3468343
9680850
71c36f2
6985672
3bead5f
5cd7bf4
707a85f
3c67918
80f22c5
82c7b1e
798d10a
dd694c5
d9a554a
226d8e6
2af1964
b4192ad
6a6edd6
b95d5b2
351c57b
bc62be0
6056901
436aa8e
7a463c1
bbe2475
fdda64d
7561a4d
c1c192b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,6 @@ | ||
| import 'package:flutter/material.dart'; | ||
|
|
||
| import 'src/app.dart'; | ||
|
|
||
| void main() async { | ||
| void main() { | ||
| runApp(App()); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import 'dart:async'; | ||
|
|
||
| import 'package:bloc/bloc.dart'; | ||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||
| import 'package:polydodo/src/domain/acquisition_device/acquisition_device.dart'; | ||
| import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; | ||
| import 'device_selector_state.dart'; | ||
|
|
||
| class DeviceSelectorCubit extends Cubit<DeviceState> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AcquisitionDeviceConnectionCubit? et le state devrait reprendre le nom complet, même si c'est long, histoire de clareté (surtout que dans l'app on peut se ramasser avec beaucoup de noms semblable donc autant être précis 🎯 ). DeviceState => AcquisitionDeviceConnectionState. |
||
| final IAcquisitionDeviceRepository _deviceRepository; | ||
|
|
||
| StreamSubscription<List<AcquisitionDevice>> _acquisitionDeviceStream; | ||
|
|
||
| DeviceSelectorCubit(this._deviceRepository) : super(DeviceInitial()) { | ||
| startSearching(); | ||
| } | ||
|
|
||
| void startSearching() { | ||
| _deviceRepository.initializeRepository(); | ||
|
|
||
| if (_acquisitionDeviceStream == null) { | ||
| _acquisitionDeviceStream = _deviceRepository | ||
| .watch() | ||
| .asBroadcastStream() | ||
| .listen((devices) => emit(DeviceSearchInProgress(devices)), | ||
| onError: (e) => emit(DeviceSearchFailure(e))); | ||
| } | ||
| } | ||
|
|
||
| void connect(AcquisitionDevice device) async { | ||
| emit(DeviceConnectionInProgress()); | ||
|
|
||
| _deviceRepository.connect(device).then( | ||
| (value) => { | ||
| _acquisitionDeviceStream.cancel(), | ||
| emit(DeviceConnectionSuccess()) | ||
| }, | ||
| onError: (e) => {emit(DeviceConnectionFailure(e)), resetSearch()}); | ||
| } | ||
|
|
||
| void resetSearch() { | ||
| _deviceRepository.disconnect(); | ||
| startSearching(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import 'package:polydodo/src/domain/acquisition_device/acquisition_device.dart'; | ||
|
|
||
| abstract class DeviceState {} | ||
|
|
||
| class DeviceInitial extends DeviceState {} | ||
|
|
||
| class DeviceSearchInProgress extends DeviceState { | ||
| final List<AcquisitionDevice> devices; | ||
|
|
||
| DeviceSearchInProgress(this.devices); | ||
| } | ||
|
|
||
| class DeviceSearchFailure extends DeviceState { | ||
| final Exception cause; | ||
|
|
||
| DeviceSearchFailure(this.cause); | ||
| } | ||
|
|
||
| class DeviceConnectionInProgress extends DeviceState {} | ||
|
|
||
| class DeviceConnectionSuccess extends DeviceState {} | ||
|
|
||
| class DeviceConnectionFailure extends DeviceState { | ||
| final Exception cause; | ||
|
|
||
| DeviceConnectionFailure(this.cause); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; | ||
| import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; | ||
|
|
||
| import 'data_states.dart'; | ||
| import 'package:bloc/bloc.dart'; | ||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||
|
|
||
| class DataCubit extends Cubit<DataState> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. EegDataRecordingCubit? |
||
| final IAcquisitionDeviceRepository _deviceRepository; | ||
| final IEEGDataRepository _eegDataRepository; | ||
|
|
||
| DataCubit(this._deviceRepository, this._eegDataRepository) | ||
| : super(DataStateInitial()); | ||
|
|
||
| void startStreaming() { | ||
| emit(DataStateRecording()); | ||
| _deviceRepository | ||
| .startDataStream() | ||
| .then((stream) => _eegDataRepository.createRecordingFromStream(stream)); | ||
| } | ||
|
|
||
| void stopStreaming() { | ||
| emit(DataStateInitial()); | ||
| _deviceRepository.stopDataStream(); | ||
| _eegDataRepository.stopRecordingFromStream(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| abstract class DataState {} | ||
|
|
||
| class DataStateInitial extends DataState {} | ||
|
|
||
| class DataStateRecording extends DataState {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import 'package:equatable/equatable.dart'; | ||
| import 'package:polydodo/src/domain/entity.dart'; | ||
|
|
||
| import '../unique_id.dart'; | ||
|
|
||
| class AcquisitionDevice extends Entity { | ||
| final String name; | ||
|
|
||
| AcquisitionDevice( | ||
| id, | ||
| this.name, | ||
| ) : super(id); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import 'acquisition_device.dart'; | ||
|
|
||
| abstract class IAcquisitionDeviceRepository { | ||
| void initializeRepository(); | ||
|
|
||
| Future<void> connect(AcquisitionDevice device); | ||
| void disconnect(); | ||
|
|
||
| Future<Stream<List<int>>> startDataStream(); | ||
| void stopDataStream(); | ||
|
|
||
| Stream<List<AcquisitionDevice>> watch(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| // EEGData can be extended later to add our metrics | ||
| import '../unique_id.dart'; | ||
|
|
||
| class EEGData { | ||
| UniqueId id; | ||
| List<List> _values; | ||
|
MouradLachhab marked this conversation as resolved.
|
||
| int sampleCounter = 0; | ||
|
|
||
| EEGData(this.id, this._values) | ||
| : assert(id != null), | ||
| assert(_values != null); | ||
|
|
||
| List<List> get values => _values; | ||
|
|
||
| String get fileName => id.toString(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| abstract class IEEGDataRepository { | ||
| void createRecordingFromStream(Stream<List<int>> stream); | ||
| void stopRecordingFromStream(); | ||
|
|
||
| // todo: implement export and import | ||
| void importData(); | ||
| void exportData(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import 'package:equatable/equatable.dart'; | ||
| import 'package:polydodo/src/domain/unique_id.dart'; | ||
|
|
||
| abstract class Entity extends Equatable { | ||
| final UniqueId id; | ||
|
|
||
| Entity(this.id); | ||
|
|
||
| @override | ||
| List<Object> get props => [id]; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| import 'dart:async'; | ||
|
|
||
| import 'package:flutter/services.dart'; | ||
| import 'package:flutter_blue/flutter_blue.dart'; | ||
| import 'package:polydodo/src/domain/acquisition_device/acquisition_device.dart'; | ||
| import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; | ||
| import 'package:polydodo/src/domain/unique_id.dart'; | ||
|
|
||
| class BluetoothRepository implements IAcquisitionDeviceRepository { | ||
|
MouradLachhab marked this conversation as resolved.
|
||
| static const String BLE_SERVICE = "fe84"; | ||
| static const String BLE_RECEIVE = "2d30c082"; | ||
| static const String BLE_SEND = "2d30c083"; | ||
| static const startStreamChar = 'b'; | ||
| static const stopStreamChar = 's'; | ||
|
|
||
| BluetoothDevice _selectedDevice; | ||
| BluetoothCharacteristic _sendCharacteristic; | ||
| BluetoothCharacteristic _receiveCharacteristic; | ||
|
|
||
| FlutterBlue flutterBlue; | ||
| StreamSubscription<List<ScanResult>> _bluetoothScanSubscription; | ||
| List<AcquisitionDevice> _acquisitionDevicePersistency = []; | ||
| List<BluetoothDevice> _bluetoothDevices = []; | ||
| final streamController = StreamController<List<AcquisitionDevice>>(); | ||
|
|
||
| BluetoothRepository(); | ||
|
|
||
| void initializeRepository() { | ||
| if (_bluetoothScanSubscription == null) { | ||
| flutterBlue = FlutterBlue.instance; | ||
|
MouradLachhab marked this conversation as resolved.
|
||
|
|
||
| flutterBlue.connectedDevices | ||
| .asStream() | ||
| .asBroadcastStream() | ||
| .listen((List<BluetoothDevice> devices) { | ||
| for (BluetoothDevice device in devices) { | ||
| addDevice(device); | ||
| } | ||
| }); | ||
| _bluetoothScanSubscription = | ||
| flutterBlue.scanResults.listen((List<ScanResult> results) { | ||
| for (ScanResult result in results) { | ||
| addDevice(result.device); | ||
| } | ||
| }); | ||
| } else { | ||
| _bluetoothScanSubscription.resume(); | ||
| } | ||
| flutterBlue.startScan(); | ||
| } | ||
|
|
||
| void addDevice(BluetoothDevice bluetoothDevice) { | ||
| AcquisitionDevice device = AcquisitionDevice( | ||
| UniqueId.from(bluetoothDevice.id.toString()), bluetoothDevice.name); | ||
|
|
||
| final idx = _acquisitionDevicePersistency.indexOf(device); | ||
|
|
||
| if (idx == -1) { | ||
| _acquisitionDevicePersistency.add(device); | ||
| _bluetoothDevices.add(bluetoothDevice); | ||
| } else { | ||
| _acquisitionDevicePersistency[idx] = device; | ||
| _bluetoothDevices[idx] = bluetoothDevice; | ||
| } | ||
|
|
||
| streamController.add(_acquisitionDevicePersistency); | ||
| } | ||
|
|
||
| Future<void> connect(AcquisitionDevice device) async { | ||
| _selectedDevice = | ||
| _bluetoothDevices[_acquisitionDevicePersistency.indexOf(device)]; | ||
|
|
||
| _acquisitionDevicePersistency.clear(); | ||
| _bluetoothDevices.clear(); | ||
| _bluetoothScanSubscription.pause(); | ||
| flutterBlue.stopScan(); | ||
|
|
||
| try { | ||
| await _selectedDevice | ||
| .connect() | ||
| .then((value) => findRelevantCharacteristics()) | ||
| .timeout(Duration(seconds: 6), | ||
| onTimeout: () => | ||
| {disconnect(), throw Exception("Connection Timed out")}); | ||
| } catch (e) { | ||
| if (e is PlatformException) { | ||
| if (e.code != "already_connected") throw Exception(e.details); | ||
| } else | ||
| throw e; | ||
| } | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| void disconnect() async { | ||
| if (_selectedDevice != null) { | ||
| await _selectedDevice.disconnect(); | ||
| _selectedDevice = null; | ||
| } | ||
| } | ||
|
|
||
| void findRelevantCharacteristics() { | ||
| _selectedDevice.discoverServices().then((services) => { | ||
| for (BluetoothCharacteristic characteristic in (services.where( | ||
| (service) => service.uuid.toString().contains(BLE_SERVICE))) | ||
| .first | ||
| .characteristics) | ||
| { | ||
| if (characteristic.uuid.toString().contains(BLE_RECEIVE)) | ||
| {_receiveCharacteristic = characteristic} | ||
| else if (characteristic.uuid.toString().contains(BLE_SEND)) | ||
| {_sendCharacteristic = characteristic} | ||
| }, | ||
| if (_receiveCharacteristic == null) | ||
| throw Exception('Device is missing receive Characteristic'), | ||
| if (_sendCharacteristic == null) | ||
| throw Exception('Device is missing send Characteristic') | ||
| }); | ||
| } | ||
|
|
||
| Future<Stream<List<int>>> startDataStream() async { | ||
| await _receiveCharacteristic.setNotifyValue(true); | ||
|
|
||
| await _sendCharacteristic.write(startStreamChar.codeUnits); | ||
| return _receiveCharacteristic.value; | ||
| } | ||
|
|
||
| void stopDataStream() async { | ||
| await _receiveCharacteristic.setNotifyValue(false); | ||
| await _sendCharacteristic.write(stopStreamChar.codeUnits); | ||
| } | ||
|
|
||
| @override | ||
| Stream<List<AcquisitionDevice>> watch() => streamController.stream; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| const OPEN_BCI_HEADER = [ | ||
| ["%OpenBCI Raw EEG Data"], | ||
| ["%Number of channels = 4"], | ||
| ["%Sample Rate = 200 Hz"], | ||
| ["%Board = OpenBCI_GUI\$BoardGanglionBLE"], | ||
| [ | ||
| "Sample Index", | ||
| " EXG Channel 0", | ||
| " EXG Channel 1", | ||
| " EXG Channel 2", | ||
| " EXG Channel 3", | ||
| " Accel Channel 0", | ||
| " Accel Channel 1", | ||
| " Accel Channel 2", | ||
| " Other", | ||
| " Other", | ||
| " Other", | ||
| " Other", | ||
| " Other", | ||
| " Timestamp", | ||
| " Timestamp (Formatted)" | ||
| ] | ||
| ]; |
Uh oh!
There was an error while loading. Please reload this page.